Đếm DISTINCT trên nhiều cột


212

Có cách nào tốt hơn để thực hiện một truy vấn như thế này:

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

Tôi cần đếm số lượng các mục riêng biệt từ bảng này nhưng sự khác biệt là trên hai cột.

Truy vấn của tôi hoạt động tốt nhưng tôi đã tự hỏi liệu tôi có thể nhận được kết quả cuối cùng chỉ bằng một truy vấn (không sử dụng truy vấn phụ)


IordanTanev, Mark Brackett, RC - cảm ơn vì đã trả lời, đây là một thử rất hay, nhưng bạn cần kiểm tra những gì bạn làm trước khi đăng lên SO. Các truy vấn bạn cung cấp không tương đương với truy vấn của tôi. Bạn có thể dễ dàng thấy tôi luôn có kết quả vô hướng nhưng truy vấn của bạn trả về nhiều hàng.
Novitzky

Chỉ cần cập nhật câu hỏi để bao gồm nhận xét làm rõ của bạn từ một trong những câu trả lời
Jeff


Đây là một câu hỏi hay. Tôi cũng tự hỏi liệu có cách nào đơn giản hơn để làm việc này không
Anupam

Câu trả lời:


73

Nếu bạn đang cố gắng cải thiện hiệu suất, bạn có thể thử tạo một cột được tính toán bền vững trên giá trị băm hoặc nối của hai cột.

Khi nó được duy trì, với điều kiện cột là xác định và bạn đang sử dụng cài đặt cơ sở dữ liệu "lành mạnh", nó có thể được lập chỉ mục và / hoặc thống kê có thể được tạo trên đó.

Tôi tin rằng một số lượng riêng biệt của cột được tính sẽ tương đương với truy vấn của bạn.


4
Đề nghị tuyệt vời! Càng đọc, tôi càng nhận ra rằng SQL ít biết về cú pháp và hàm và nhiều hơn về việc áp dụng logic thuần túy .. Tôi ước tôi có 2 lần nâng cấp!
tumchaaditya

Đề nghị quá tốt. Nó tránh tôi để viết mã không cần thiết cho điều này.
Avrajit Roy

1
Bạn có vui lòng thêm một ví dụ hoặc mẫu mã để hiển thị thêm về ý nghĩa của việc này và cách thực hiện không?
jayqui

52

Chỉnh sửa: Thay đổi từ truy vấn chỉ kiểm tra ít đáng tin cậy Tôi đã phát hiện ra một cách để làm điều này (trong SQL Server 2005) hoạt động khá tốt đối với tôi và tôi có thể sử dụng nhiều cột như tôi cần (bằng cách thêm chúng vào hàm CHECKSUM ()). Hàm REVERSE () biến ints thành varchars để làm cho sự khác biệt đáng tin cậy hơn

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

1
+1 Một cái đẹp, hoạt động hoàn hảo (khi bạn có các loại cột phù hợp để thực hiện CheckSum vào ...;)
Bernoulli IT

8
Với các giá trị băm như Checksum (), có khả năng nhỏ là cùng một hàm băm sẽ được trả về cho các đầu vào khác nhau để số lượng có thể bị giảm đi một chút. HashBytes () là một cơ hội thậm chí nhỏ hơn nhưng vẫn không phải là không. Nếu hai Id đó là int's (32b) thì "hashless lossless" có thể kết hợp chúng thành một bigint (64b) như Id1 << 32 + Id2.
crokusek

1
cơ hội không phải là quá nhỏ, đặc biệt là khi bạn bắt đầu kết hợp các cột (đó là ý nghĩa của nó). Tôi đã tò mò về cách tiếp cận này và trong một trường hợp cụ thể, tổng kiểm tra kết thúc với số lượng nhỏ hơn 10%. Nếu bạn nghĩ về nó lâu hơn một chút, Checksum chỉ trả về một số nguyên, vì vậy nếu bạn kiểm tra toàn bộ phạm vi bigint, bạn sẽ có một số lượng nhỏ hơn khoảng 2 tỷ lần so với thực tế. -1
pvolders

Đã cập nhật truy vấn để bao gồm việc sử dụng "REVERSE" để loại bỏ cơ hội trùng lặp
JayTee

4
Chúng ta có thể tránh KIỂM TRA - chúng ta có thể ghép hai giá trị lại với nhau không? Tôi cho rằng các rủi ro được coi là điều tương tự: ('anh ấy', 'nghệ thuật') == 'nghe', 't'). Nhưng tôi nghĩ rằng điều đó có thể được giải quyết bằng một dấu phân cách như @APC đề xuất (một số giá trị không xuất hiện trong một trong hai cột), vì vậy 'anh ấy | nghệ thuật'! = 'Nghe | t' Có những vấn đề khác với "ghép" đơn giản tiếp cận?
Hạt đậu đỏ

31

Điều gì về truy vấn hiện tại của bạn mà bạn không thích? Nếu bạn lo ngại rằng DISTINCTtrên hai cột không trả về các hoán vị duy nhất tại sao không thử nó?

Nó chắc chắn hoạt động như bạn có thể mong đợi trong 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>

biên tập

Tôi đi vào một con hẻm mù với các phân tích nhưng câu trả lời thật rõ ràng ...

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

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

SQL>

chỉnh sửa 2

Đưa ra các dữ liệu sau đây, giải pháp nối được cung cấp ở trên sẽ bị sai lệch:

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

Vì vậy, chúng tôi bao gồm một dải phân cách ...

select col1 + '*' + col2 from t23
/

Rõ ràng dấu phân cách được chọn phải là một ký tự hoặc tập hợp các ký tự không bao giờ xuất hiện trong một trong hai cột.


+1 từ tôi. Cảm ơn câu trả lời của bạn. Truy vấn của tôi hoạt động tốt nhưng tôi đã tự hỏi liệu tôi có thể nhận được kết quả cuối cùng chỉ bằng một truy vấn (không sử dụng truy vấn phụ)
Novitzky

19

Để chạy dưới dạng một truy vấn duy nhất, nối các cột, sau đó lấy số lượng phiên bản riêng biệt của chuỗi được nối.

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

Trong MySQL, bạn có thể làm điều tương tự mà không cần bước nối như sau:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Tính năng này được đề cập trong tài liệu MySQL:

http://dev.mysql.com/doc/refman/5.7/en/group-by-fifts.html#feft_count-distotype


Đây là một câu hỏi về SQL Server và cả hai tùy chọn bạn đã đăng đã được đề cập trong các câu trả lời sau cho câu hỏi này: stackoverflow.com/a/1471444/4955425stackoverflow.com/a/1471713/4955425 .
sstan

1
FWIW, điều này gần như hoạt động trong PostgreSQL; chỉ cần thêm dấu ngoặc đơn:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph

14

Làm thế nào về một cái gì đó như:

chọn tính (*)
từ
  (chọn tính (*) cnt
   từ DocumentOutputItems
   nhóm theo DocumentId, DocumentSessionId) t1

Có lẽ chỉ cần làm giống như bạn đã có nhưng nó tránh được DISTINCT.


trong các thử nghiệm của tôi (sử dụng SET SHOWPLAN_ALL ON), nó có cùng một kế hoạch thực hiện và chính xác là TotalSubtreeCost
KM.

1
Tùy thuộc vào độ phức tạp của truy vấn ban đầu, việc giải quyết vấn đề này có GROUP BYthể đưa ra một vài thách thức bổ sung cho việc chuyển đổi truy vấn để đạt được đầu ra mong muốn (ví dụ: khi truy vấn ban đầu đã có GROUP BYhoặc HAVINGmệnh đề ...)
Lukas Eder

8

Đây là phiên bản ngắn hơn mà không có phần phụ:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

Nó hoạt động tốt trong MySQL và tôi nghĩ rằng trình tối ưu hóa có thời gian dễ hiểu hơn về điều này.

Chỉnh sửa: Rõ ràng tôi đã đọc sai MSSQL và MySQL - xin lỗi về điều đó, nhưng có lẽ nó vẫn giúp ích.


6
trong SQL Server, bạn nhận được: Msg 102, Cấp 15, Trạng thái 1, Dòng 1 Cú pháp không chính xác gần ','.
KM.

Đây là những gì tôi đã nghĩ đến. Tôi muốn làm điều tương tự trong MSSQL nếu có thể.
Novitzky

@Kamil Nowicki, trong SQL Server, bạn chỉ có thể có một trường trong COUNT (), trong câu trả lời của tôi, tôi cho thấy rằng bạn có thể ghép hai trường thành một và thử phương pháp này. Tuy nhiên, tôi chỉ gắn bó với bản gốc vì các kế hoạch truy vấn sẽ kết thúc giống nhau.
KM.

1
Xin vui lòng xem trong câu trả lời @JayTee. Nó hoạt động như một say mê. count ( distinct CHECKSUM ([Field1], [Field2])
Custodio

5

Nhiều cơ sở dữ liệu SQL có thể hoạt động với các bộ dữ liệu như các giá trị để bạn có thể thực hiện: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; Nếu cơ sở dữ liệu của bạn không hỗ trợ điều này, thì nó có thể được mô phỏng theo đề xuất của CHECKSUM theo hàm @ oncel-umut-turer hoặc hàm vô hướng khác cung cấp tính duy nhất tốt ví dụ COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Việc sử dụng các bộ dữ liệu có liên quan đang thực hiện INcác truy vấn như: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));


cơ sở dữ liệu nào hỗ trợ select count(distinct(a, b))? : D
Vytenis Bivainis

@VytenisBivainis Tôi biết PostgreSQL không - không biết từ phiên bản nào.
karmakaze

3

Không có gì sai với truy vấn của bạn, nhưng bạn cũng có thể làm theo cách này:

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

3

Hy vọng tác phẩm này tôi đang viết trên prima vista

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

7
Để điều này đưa ra câu trả lời cuối cùng, bạn sẽ phải bọc nó trong một QUẬN CHỌN (*) TỪ (...) khác. Về cơ bản, câu trả lời này chỉ cung cấp cho bạn một cách khác để liệt kê các giá trị riêng biệt mà bạn muốn tính. Nó không tốt hơn giải pháp ban đầu của bạn.
Dave Costa

Cảm ơn Dave. Tôi biết bạn có thể sử dụng nhóm bằng cách thay vì khác biệt trong trường hợp của tôi. Tôi đã tự hỏi nếu bạn nhận được kết quả cuối cùng chỉ bằng một truy vấn. Tôi nghĩ là không thể nhưng tôi có thể sai.
Novitzky

3

Tôi đã sử dụng phương pháp này và nó đã làm việc cho tôi.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

Đối với trường hợp của tôi, nó cung cấp kết quả chính xác.


Nó không cung cấp cho bạn số lượng giá trị riêng biệt kết hợp với hai cột. Ít nhất là không phải trong MySQL 5.8.
Anwar Shaikh

Câu hỏi này được gắn thẻ Máy chủ SQL và đây không phải là cú pháp Máy chủ SQL
Tab Alleman

2

nếu bạn chỉ có một trường để "DISTINCT", bạn có thể sử dụng:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

và điều đó sẽ trả về cùng một kế hoạch truy vấn như ban đầu, như đã được thử nghiệm với SET SHOWPLAN_ALL ON. Tuy nhiên, bạn đang sử dụng hai trường để bạn có thể thử một cái gì đó điên rồ như:

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

nhưng bạn sẽ gặp vấn đề nếu có liên quan đến NULL. Tôi chỉ cần gắn bó với truy vấn ban đầu.


+1 từ tôi. Cảm ơn nhưng tôi sẽ gắn bó với truy vấn của tôi như bạn đề xuất. Sử dụng "chuyển đổi" có thể làm giảm hiệu suất hơn nữa.
Novitzky

2

Tôi đã tìm thấy điều này khi tôi giải quyết vấn đề của riêng mình, thấy rằng nếu bạn đếm các đối tượng DISTINCT, bạn sẽ nhận được số chính xác được trả về (Tôi đang sử dụng MySQL)

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

5
Truy vấn trên sẽ trả về một tập hợp kết quả khác với những gì OP đang tìm kiếm (sự kết hợp khác biệt của DocumentIdDocumentSessionId). Alexander Kjäll đã đăng câu trả lời đúng nếu OP đang sử dụng MySQL chứ không phải MS SQL Server.
Anthony Geoghegan

1

Tôi ước MS SQL cũng có thể làm một cái gì đó như COUNT (DISTINCT A, B). Nhưng nó không thể.

Lúc đầu, câu trả lời của JayTee có vẻ như là một giải pháp cho tôi sau khi một số thử nghiệm CHECKSUM () không thể tạo ra các giá trị duy nhất. Một ví dụ nhanh là, cả CHECKSUM (31,467,519) và CHECKSUM (69,1120,823) đều cho cùng một câu trả lời là 55.

Sau đó, tôi đã thực hiện một số nghiên cứu và thấy rằng Microsoft KHÔNG khuyên bạn nên sử dụng CHECKSUM cho mục đích phát hiện thay đổi. Trong một số diễn đàn, một số gợi ý sử dụng

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

nhưng điều này cũng không phải là conforting.

Bạn có thể sử dụng hàm HASHBYTES () như được đề xuất trong câu hỏi hóc búa TSQL CHECKSUM . Tuy nhiên điều này cũng có một cơ hội nhỏ không trả lại kết quả duy nhất.

Tôi sẽ đề nghị sử dụng

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

1

Còn cái này thì sao,

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

Điều này sẽ giúp chúng tôi đếm được tất cả các kết hợp có thể có của DocumentId và DocumentSessionId


0

Nó làm việc cho tôi. Trong lời tiên tri:

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

Trong jpql:

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

0

Tôi có một câu hỏi tương tự nhưng truy vấn tôi có là một truy vấn phụ với dữ liệu so sánh trong truy vấn chính. cái gì đó như:

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

Bỏ qua sự phức tạp của điều này, tôi nhận ra rằng tôi không thể đưa giá trị của a.code vào truy vấn con với truy vấn con kép được mô tả trong câu hỏi ban đầu

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

Vì vậy, cuối cùng tôi đã tìm ra tôi có thể gian lận và kết hợp các cột:

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

Đây là những gì đã kết thúc làm việc


0

Nếu bạn đang làm việc với các kiểu dữ liệu có độ dài cố định, bạn có thể sử dụng binaryđể thực hiện việc này rất dễ dàng và rất nhanh chóng. Giả sử DocumentIdDocumentSessionIdđều là ints, và do đó dài 4 byte ...

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

Vấn đề cụ thể của tôi yêu cầu tôi để chia SUMbởi COUNTcác sự kết hợp khác nhau của các phím nước ngoài khác nhau và một trường dữ liệu, nhóm bởi một chính nước ngoài và đôi khi lọc theo giá trị hoặc một số phím. Bảng rất lớn và sử dụng truy vấn phụ làm tăng đáng kể thời gian truy vấn. Và do sự phức tạp, số liệu thống kê đơn giản không phải là một lựa chọn khả thi. Các CHECKSUMGiải pháp này cũng đã quá chậm trong việc chuyển đổi của nó, đặc biệt là kết quả của các kiểu dữ liệu khác nhau, và tôi không thể mạo hiểm không đáng tin cậy của nó.

Tuy nhiên, sử dụng giải pháp trên hầu như không tăng thời gian truy vấn (so với sử dụng đơn giản là SUM) và nên hoàn toàn đáng tin cậy! Nó có thể giúp những người khác trong tình huống tương tự vì vậy tôi sẽ đăng nó ở đây.


-1

Bạn chỉ có thể sử dụng chức năng Count Twice.

Trong trường hợp này, nó sẽ là:

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

điều này không làm theo yêu cầu trong câu hỏi, nó tính riêng biệt cho từng cột
naviram

-1

Mã này sử dụng riêng biệt trên 2 tham số và cung cấp số lượng hàng cụ thể cho số lượng hàng giá trị riêng biệt đó. Nó làm việc cho tôi trong MySQL như một cơ duyên.

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.