计算多列的DISTINCT


212

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

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

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

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


RC的Mark Brackett的IordanTanev-感谢您的答复,这是一次不错的尝试,但是您需要先检查自己在做什么,然后再发布到SO。您提供的查询与我的查询不相同。您可以轻松地看到我的结果始终是一个标量,但是您的查询返回了多行。
09年

刚刚更新了问题,以包括您对其中一个答案的澄清评论
Jeff


这是一个很好的问题。我也想知道是否有更简单的方法可以做到这一点
Anupam

Answers:


73

如果要提高性能,则可以尝试在两个列的哈希值或串联值上创建一个持久化的计算列。

一旦保留,只要该列是确定性的并且您正在使用“合理的”数据库设置,就可以对其进行索引和/或在其上创建统计信息。

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


4
很好的建议!我读得越多,我就越意识到SQL不再是关于语法和函数的了解,而更多地是关于应用纯逻辑的。.我希望我有2个赞!
tumchaaditya

很好的建议。它避免了我为此编写不必要的代码。
Avrajit Roy

1
您是否可以添加示例或代码示例以显示更多有关这意味着什么以及如何执行的信息?
jayqui

52

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

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

1
+1不错,效果完美(当您具有正确的列类型以在...上执行CheckSum时;)
Bernoulli IT

8
使用Checksum()之类的哈希,将很少有机会为不同的输入返回相同的哈希,因此计数可能会略有减少。HashBytes()的机会更小,但仍不为零。如果这两个Id是int的(32b),则“无损哈希”可以将它们组合成bigint(64b),例如Id1 << 32 + Id2。
crokusek 2014年

1
即使是这样,机会也不是很小,尤其是当您开始组合列时(这本来是应该的)。我对此方法感到好奇,在特定情况下,校验和最终减少了10%。如果您考虑更长的时间,那么Checksum只会返回一个int值,因此,如果您对一个完整的bigint范围进行校验和,您将得到一个比实际小20亿倍的独特计数。-1
pvolders

更新了查询,以包括使用“ REVERSE”来消除重复的机会
JayTee 2014年

4
我们可以避免CHECKSUM吗?我们可以将两个值连接在一起吗?我想冒着考虑同一件事的风险:(“他”,“艺术”)==“听”,“ t”)。但是我认为可以使用@APC建议的定界符(某些值不会出现在任何列中)解决,所以'he | art'!='hear | t'简单的“连接”还有其他问题吗?方法?
红豌豆

31

您不喜欢的现有查询有什么用?如果您担心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。感谢您的回答。我的查询工作正常,但我想知道是否可以仅使用一个查询(不使用子查询)获得最终结果
Novitzky,2009年

19

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

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/zh-CN/group-by-functions.html#function_count-distinct


这是一个SQL Server问题,您在以下问题的答案中都提到了您发布的两个选项:stackoverflow.com/a/1471444/4955425stackoverflow.com/a/1471713/4955425
斯坦(Sstan)2013年

1
FWIW,这几乎可以在PostgreSQL中工作;只是需要额外的括号:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph

14

怎么样:

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

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


在我的测试中(使用SET SHOWPLAN_ALL ON),它具有相同的执行计划和完全相同的TotalSubtreeCost
KM。

1
根据原始查询的复杂性,解决此问题GROUP BY可能会给查询转换带来一些其他挑战,以实现所需的输出(例如,当原始查询已经具有GROUP BYHAVING子句...时)
Lukas Eder 2013年

8

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

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

它在MySQL中运行良好,我认为优化器可以更轻松地了解这一点。

编辑:显然我误读了MSSQL和MySQL-对此感到抱歉,但是无论如何它还是有帮助的。


6
在SQL Server中,您得到:消息102,级别15,状态1,行1“,”附近的语法不正确。
KM。

这就是我的想法。如果可能,我想在MSSQL中做类似的事情。
09年

@Kamil Nowicki,在SQL Server中,您在COUNT()中只能有一个字段,在我的回答中,我表明您可以将两个字段连接为一个并尝试这种方法。但是,我会坚持使用原始版本,因为查询计划最终将是相同的。
KM。

1
请看看@JayTee答案。它像一种魅力。count ( distinct CHECKSUM ([Field1], [Field2])
Custodio

5

许多(大多数?)SQL数据库可以使用类似值的元组,因此您可以这样做: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; 如果数据库不支持此功能,则可以按照@ oncel-umut-turer建议的CHECKSUM或其他标量函数进行模拟,以提供良好的唯一性例如 COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId))

元组的相关用法正在执行IN查询,例如: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));


哪些数据库支持select count(distinct(a, b))?:D
Vytenis Bivainis '18 -10-11

@VytenisBivainis我知道PostgreSQL可以-不知道从哪个版本开始。
karmakaze

3

您的查询没有任何问题,但是您也可以这样进行:

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

3

希望我在prima vista上写这篇作品

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

7
为了给出最终答案,您必须将其包装在另一个SELECT COUNT(*)FROM(...)中。本质上,此答案只是为您提供了另一种列出您要计数的不同值的方法。没有比您原始的解决方案更好的了。
戴夫·科斯塔

谢谢戴夫。我知道在我的情况下,您可以使用分组依据而不是与众不同。我想知道您是否仅使用一个查询就能得到最终结果。我认为这是不可能的,但我可能是错的。
09年

3

我已经使用了这种方法,并且对我有用。

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

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


它不能为您提供两列相结合的不同值的计数。至少在MySQL 5.8中没有。
安瓦尔·谢赫

这个问题被标记为SQL Server,这不是SQL Server语法
Tab Alleman

2

如果只有一个字段要“ 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。谢谢,但我会按照您的建议坚持查询。使用“转换”会进一步降低性能。
09年

2

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

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

5
上面的查询将返回一组不同的结果比什么OP一直在寻找(的独特组合DocumentIdDocumentSessionId)。如果OP使用的是MySQL而不是MS SQL Server,则AlexanderKjäll已经发布了正确的答案。
Anthony Geoghegan 2014年

1

我希望MS SQL也可以做类似COUNT(DISTINCT A,B)的操作。但是不能。

最初,JayTee的答案似乎对我来说是一个解决方案,因为一些测试CHECKSUM()无法创建唯一值。一个简单的例子是,CHECKSUM(31,467,519)和CHECKSUM(69,1120,823)给出的答案都是55。

然后,我进行了一些研究,发现Microsoft不建议将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

1

这个怎么样,

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

这将使我们获得所有可能的DocumentId和DocumentSessionId组合的计数


0

这个对我有用。在oracle中:

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;

0

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

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

忽略了它的复杂性,我意识到我无法通过原始问题中描述的double子查询将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...

这就是最终的工作


0

如果您使用固定长度的数据类型,则可以强制转换binary为轻松,快速地执行此操作。假设DocumentIdDocumentSessionId均为ints,因此为4个字节长...

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

我的特定问题要求我将各种外键和日期字段的不同组合除以a SUMCOUNT再按另一个外键分组,并偶尔按某些值或键进行过滤。该表非常大,使用子查询会大大增加查询时间。而且由于复杂性,统计信息根本不是一个可行的选择。的CHECKSUM解决方案的转换也太慢了,特别是由于各种数据类型的影响,我不能冒险说它的不可靠性。

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


-1

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

在这种情况下,它将是:

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

这并没有按照问题的要求进行,而是将每列的不
重复

-1

该代码在2个参数上使用了distinct,并提供了特定于那些不同值的行数的行数计数。在MySQL中对我来说就像一个魅力。

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.