【问题标题】:Counting DISTINCT over multiple columns在多列上计算 DISTINCT
【发布时间】:2010-12-01 01:43:03
【问题描述】:

有没有更好的方法来做这样的查询:

SELECT COUNT(*) 
FROM (SELECT DISTINCT DocumentId, DocumentSessionId
      FROM DocumentOutputItems) AS internalQuery

我需要计算此表中不同项的数量,但不同项超过两列。

我的查询工作正常,但我想知道是否可以仅使用一个查询(不使用子查询)获得最终结果

【问题讨论】:

  • IordanTanev,Mark Brackett,RC - 感谢您的回复,这是一个不错的尝试,但您需要在发布到 SO 之前检查您在做什么。您提供的查询不等同于我的查询。您可以很容易地看到我总是有一个标量结果,但您的查询返回多行。
  • 刚刚更新了问题,以包含您对其中一个答案的澄清评论
  • 这是个好问题。我也想知道是否有更简单的方法来做到这一点

标签: sql sql-server performance tsql query-optimization


【解决方案1】:

如果您想提高性能,可以尝试在两列的哈希值或连接值上创建一个持久计算列。

一旦它被持久化,只要该列是确定性的并且您使用的是“健全的”数据库设置,就可以对其编制索引和/或在其上创建统计信息。

我相信计算列的不同计数将等同于您的查询。

【讨论】:

  • 很好的建议!我读的越多,我就越意识到 SQL 不是关于了解语法和函数,而是更多关于应用纯逻辑。我希望我有 2 个赞成票!
  • 建议太好了。它避免了我为此编写不必要的代码。
  • 能否请您添加一个示例或代码示例来详细说明这意味着什么以及如何做到这一点?
  • 与在这些列上创建多列索引有何不同?如果这没有意义,我很抱歉。我是 SQL 新手。
  • 这个技巧如何处理哈希冲突?我认为由于冲突,哈希值的不同计数会小于真相。
【解决方案2】:

编辑:从不太可靠的仅校验和查询更改 我发现了一种方法(在 SQL Server 2005 中)对我来说效果很好,并且我可以根据需要使用尽可能多的列(通过将它们添加到 CHECKSUM() 函数)。 REVERSE() 函数将 int 转换为 varchars 以使 distinct 更可靠

SELECT COUNT(DISTINCT (CHECKSUM(DocumentId,DocumentSessionId)) + CHECKSUM(REVERSE(DocumentId),REVERSE(DocumentSessionId)) )
FROM DocumentOutPutItems

【讨论】:

  • +1 不错,完美运行(当您有正确的列类型来执行 CheckSum 时...;)
  • 对于像 Checksum() 这样的哈希,对于不同的输入返回相同哈希的可能性很小,因此计数可能会略微偏离。 HashBytes() 的机会更小,但仍然不是零。如果这两个 Id 是 int (32b),那么“无损哈希”可以将它们组合成一个 bigint (64b),例如 Id1
  • 这个机会甚至不是那么小,尤其是当您开始合并列时(这就是它的本意)。我对这种方法很好奇,在特定情况下,校验和最终减少了 10%。如果你想得更久一点,Checksum 只会返回一个 int,所以如果你对一个完整的 bigint 范围进行校验和,你最终会得到一个比实际值小 20 亿倍的不同计数。 -1
  • 更新了查询以包括使用“REVERSE”来消除重复的机会
  • 我们可以避免使用 CHECKSUM -- 我们可以将两个值连接在一起吗?我想这可能会被认为是同一件事:('he', 'art') == 'hear', 't')。但我认为这可以通过@APC 建议的分隔符来解决(一些值没有出现在任何一列中),所以 'he|art' != 'hear|t' 简单的“连接”是否还有其他问题方法?
【解决方案3】:

您不喜欢现有查询的哪些方面?如果您担心跨两列的DISTINCT 不会只返回唯一的排列,为什么不试试呢?

它确实可以在 Oracle 中正常工作。

SQL> select distinct deptno, job from emp
  2  order by deptno, job
  3  /

    DEPTNO JOB
---------- ---------
        10 CLERK
        10 MANAGER
        10 PRESIDENT
        20 ANALYST
        20 CLERK
        20 MANAGER
        30 CLERK
        30 MANAGER
        30 SALESMAN

9 rows selected.


SQL> select count(*) from (
  2  select distinct deptno, job from emp
  3  )
  4  /

  COUNT(*)
----------
         9

SQL>

编辑

我在分析方面走上了一条死胡同,但答案却很明显……

SQL> select count(distinct concat(deptno,job)) from emp
  2  /

COUNT(DISTINCTCONCAT(DEPTNO,JOB))
---------------------------------
                                9

SQL>

编辑 2

鉴于以下数据,上面提供的连接解决方​​案会误算:

col1  col2
----  ----
A     AA
AA    A

所以我们要包含一个分隔符...

select col1 + '*' + col2 from t23
/

显然,选择的分隔符必须是一个字符或一组字符,它们永远不会出现在任一列中。

【讨论】:

  • +1 来自我。感谢您的回答。我的查询工作正常,但我想知道是否可以仅使用一个查询(不使用子查询)获得最终结果
【解决方案4】:

要作为单个查询运行,请连接列,然后获取连接字符串实例的不同计数。

SELECT count(DISTINCT concat(DocumentId, DocumentSessionId)) FROM DocumentOutputItems;

在 MySQL 中,您可以在没有连接步骤的情况下执行相同的操作,如下所示:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

MySQL 文档中提到了此功能:

http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_count-distinct

【讨论】:

  • 这是一个 SQL Server 问题,您发布的这两个选项已在该问题的以下答案中提及:stackoverflow.com/a/1471444/4955425stackoverflow.com/a/1471713/4955425
  • FWIW,这几乎适用于 PostgreSQL;只需要额外的括号:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
  • 使用此方法时要非常小心,因为它可能导致错误计数。以下示例将返回计数 1。 DocumentID | DocumentSessionID "A" | "AB" "AA" | “乙”
  • 正如@Bort 所指出的,第一个选项可能导致不正确的结果,最好使用 CONCAT_WS 编写。第二种方法也不能保证产生与原始查询相同的结果,以防任何列可以为空。
【解决方案5】:

比如:

选择计数(*) 从 (选择计数(*)cnt 来自 DocumentOutputItems 按 DocumentId、DocumentSessionId 分组)t1

可能只是和你已经做的一样,但它避免了 DISTINCT。

【讨论】:

  • 在我的测试中(使用 SET SHOWPLAN_ALL ON),它具有相同的执行计划和完全相同的 TotalSubtreeCost
  • 根据原始查询的复杂性,使用GROUP BY 解决这个问题可能会给查询转换带来一些额外的挑战,以实现所需的输出(例如,当原始查询已经有GROUP BYHAVING 子句...)
【解决方案6】:

一些 SQL 数据库可以使用元组表达式,因此您可以这样做:

SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId))
      FROM DocumentOutputItems;

如果您的数据库不支持这一点,可以按照@oncel-umut-turer 对 CHECKSUM 或其他提供良好唯一性的标量函数的建议进行模拟,例如 COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

MySQL specifically supports COUNT(DISTINCT expr, expr, ...) 这是非 SQL 标准语法。它还注意到In standard SQL, you would have to do a concatenation of all expressions inside COUNT(DISTINCT ...).

元组的一个相关用途是执行IN 查询,例如:

SELECT * FROM DocumentOutputItems
WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));

【讨论】:

  • 哪些数据库支持select count(distinct(a, b))? :D
  • @VytenisBivainis 我知道 PostgreSQL 可以——不知道从哪个版本开始。
  • @VytenisBivainis MySQL 也支持这一点
  • @Sergiy 谢谢,更新了答案,链接到 MySQL 支持的非标准语法
【解决方案7】:

这是一个没有子选择的较短版本:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

它在 MySQL 中运行良好,我认为优化器更容易理解这一点。

编辑:显然我误读了 MSSQL 和 MySQL - 对此很抱歉,但无论如何它可能会有所帮助。

【讨论】:

  • 在 SQL Server 中你得到:Msg 102, Level 15, State 1, Line 1 ',' 附近的语法不正确。
  • 这就是我的想法。如果可能的话,我想在 MSSQL 中做类似的事情。
  • @Kamil Nowicki,在 SQL Server 中,您只能在 COUNT() 中有一个字段,在我的回答中,我表明您可以将这两个字段连接成一个并尝试这种方法。但是,我会坚持原来的,因为查询计划最终会是一样的。
  • 请看@JayTee 的回答。它就像一个魅力。 count ( distinct CHECKSUM ([Field1], [Field2])
  • 即使在 MySQL 中,这也不完全等同于原始查询,因为不会计算具有 NULL 的行。
【解决方案8】:

我使用过这种方法,它对我很有效。

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

就我而言,它提供了正确的结果。

【讨论】:

  • 它不会为您提供结合两列的不同值的计数。至少在 MySQL 5.8 中没有。
  • 这个问题被标记为 SQL Server,这不是 SQL Server 语法
【解决方案9】:

您的查询没有问题,但您也可以这样做:

WITH internalQuery (Amount)
AS
(
    SELECT (0)
      FROM DocumentOutputItems
  GROUP BY DocumentId, DocumentSessionId
)
SELECT COUNT(*) AS NumberOfDistinctRows
  FROM internalQuery

【讨论】:

    【解决方案10】:

    希望这是我在 prima vista 上写的作品

    SELECT COUNT(*) 
    FROM DocumentOutputItems 
    GROUP BY DocumentId, DocumentSessionId
    

    【讨论】:

    • 为了给出最终答案,您必须将其包装在另一个 SELECT COUNT(*) FROM ( ... ) 中。从本质上讲,这个答案只是为您提供了另一种列出要计算的不同值的方法。它并不比您原来的解决方案更好。
    • 谢谢戴夫。我知道在我的情况下您可以使用 group by 而不是 distinct。我想知道您是否仅使用一个查询即可获得最终结果。我认为这是不可能的,但我可能错了。
    【解决方案11】:

    如果您只有一个“DISTINCT”字段,您可以使用:

    SELECT COUNT(DISTINCT DocumentId) 
    FROM DocumentOutputItems
    

    并且确实返回与原始查询计划相同的查询计划,正如使用 SET SHOWPLAN_ALL ON 测试的那样。但是,您正在使用两个字段,因此您可以尝试一些疯狂的事情,例如:

        SELECT COUNT(DISTINCT convert(varchar(15),DocumentId)+'|~|'+convert(varchar(15), DocumentSessionId)) 
        FROM DocumentOutputItems
    

    但如果涉及 NULL,您将遇到问题。我会坚持原来的查询。

    【讨论】:

    • +1 来自我。谢谢,但我会按照你的建议坚持我的查询。使用“convert”会进一步降低性能。
    【解决方案12】:

    我在 Google 搜索自己的问题时发现了这一点,发现如果计算 DISTINCT 对象,则返回正确的数字(我使用的是 MySQL)

    SELECT COUNT(DISTINCT DocumentID) AS Count1, 
      COUNT(DISTINCT DocumentSessionId) AS Count2
      FROM DocumentOutputItems
    

    【讨论】:

    • 上述查询将返回一组与 OP 正在寻找的结果不同的结果(DocumentIdDocumentSessionId 的不同组合)。如果 OP 使用的是 MySQL 而不是 MS SQL Server,Alexander Kjäll 已经发布了正确答案。
    【解决方案13】:

    我希望 MS SQL 也可以执行 COUNT(DISTINCT A, B) 之类的操作。但它不能。

    在一些测试 CHECKSUM() 未能创建唯一值之后,起初 JayTee 的回答对我来说似乎是一个解决方案。一个简单的例子是,CHECKSUM(31,467,519) 和 CHECKSUM(69,1120,823) 给出相同的答案,即 55。

    然后我做了一些研究,发现微软不建议使用 CHECKSUM 来进行变更检测。在一些论坛中,一些人建议使用

    SELECT COUNT(DISTINCT CHECKSUM(value1, value2, ..., valueN) + CHECKSUM(valueN, value(N-1), ..., value1))
    

    但这也不是很舒服。

    您可以按照TSQL CHECKSUM conundrum 中的建议使用 HASHBYTES() 函数。但是,这也有很小的机会不会返回独特的结果。

    我建议使用

    SELECT COUNT(DISTINCT CAST(DocumentId AS VARCHAR)+'-'+CAST(DocumentSessionId AS VARCHAR)) FROM DocumentOutputItems
    

    【讨论】:

      【解决方案14】:

      这个怎么样,

      Select DocumentId, DocumentSessionId, count(*) as c 
      from DocumentOutputItems 
      group by DocumentId, DocumentSessionId;
      

      这将为我们提供 DocumentId 和 DocumentSessionId 的所有可能组合的计数

      【讨论】:

        【解决方案15】:

        如果您正在使用固定长度的数据类型,您可以转换为binary 以非常轻松快速地完成此操作。假设 DocumentIdDocumentSessionId 都是 ints,因此是 4 个字节长...

        SELECT COUNT(DISTINCT CAST(DocumentId as binary(4)) + CAST(DocumentSessionId as binary(4)))
        FROM DocumentOutputItems
        

        我的具体问题要求我将SUM 除以各种外键和日期字段的不同组合的COUNT,按另一个外键分组,偶尔按某些值或键进行过滤。表很大,使用子查询大大增加了查询时间。而且由于复杂性,统计数据根本不是一个可行的选择。 CHECKSUM 解决方案的转换速度也太慢了,特别是由于各种数据类型,我不能冒它不可靠的风险。

        但是,使用上述解决方案几乎没有增加查询时间(与仅使用SUM 相比),并且应该是完全可靠的!它应该能够帮助处于类似情况的其他人,所以我将其发布在这里。

        【讨论】:

          【解决方案16】:

          它对我有用。在甲骨文中:

          SELECT SUM(DECODE(COUNT(*),1,1,1))
          FROM DocumentOutputItems GROUP BY DocumentId, DocumentSessionId;
          

          在 jpql 中:

          SELECT SUM(CASE WHEN COUNT(i)=1 THEN 1 ELSE 1 END)
          FROM DocumentOutputItems i GROUP BY i.DocumentId, i.DocumentSessionId;
          

          【讨论】:

          • 这个问题与 Oracle 无关。这是关于 SQL Server 的。
          【解决方案17】:

          我有一个类似的问题,但我的查询是一个子查询,其中包含主查询中的比较数据。类似:

          Select code, id, title, name 
          (select count(distinct col1) from mytable where code = a.code and length(title) >0)
          from mytable a
          group by code, id, title, name
          --needs distinct over col2 as well as col1
          

          忽略这一点的复杂性,我意识到我无法使用原始问题中描述的双子查询将 a.code 的值放入子查询中

          Select count(1) from (select distinct col1, col2 from mytable where code = a.code...)
          --this doesn't work because the sub-query doesn't know what "a" is
          

          所以最终我发现我可以作弊,并合并列:

          Select count(distinct(col1 || col2)) from mytable where code = a.code...
          

          这就是最终的工作

          【讨论】:

          • count(1) 中的 1 是什么意思?
          • @devloper152:没有特殊含义。出于某种原因,count() 总是需要争论,所以根据人们的口味,他们通常会使用count(*)count(1)count(null)
          • 需要明确的是,|| 在某些数据库中是一个连接运算符。这个问题是关于 SQL Server 的,+ 是等价的。就像这个问题上的所有其他答案推荐连接一样,这会遇到不同值('a'、'bc' vs 'ab'、'c')的组合可以连接到相同值('abc')的问题,给你一个不正确的计数。
          【解决方案18】:

          此代码在 2 个参数上使用 distinct,并提供特定于这些不同值行数的行数。它在 MySQL 中对我很有用。

          select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
          from DocumentOutputItems   
          group by i ,s;
          

          【讨论】:

            【解决方案19】:

            您可以只使用两次计数功能。

            在这种情况下,它将是:

            SELECT COUNT (DISTINCT DocumentId), COUNT (DISTINCT DocumentSessionId) 
            FROM DocumentOutputItems
            

            【讨论】:

            • 这不符合问题的要求,它计算每列的不同
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-03-03
            • 1970-01-01
            • 2023-01-12
            • 1970-01-01
            • 2018-07-01
            • 2021-09-26
            • 1970-01-01
            相关资源
            最近更新 更多