การนับ DISTINCT บนหลายคอลัมน์


213

มีวิธีที่ดีกว่าในการทำแบบสอบถามเช่นนี้:

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

ฉันต้องนับจำนวนรายการที่แตกต่างจากตารางนี้ แต่ความแตกต่างอยู่เหนือสองคอลัมน์

แบบสอบถามของฉันทำงานได้ดี แต่ฉันสงสัยว่าฉันจะได้ผลลัพธ์สุดท้ายโดยใช้เพียงแบบสอบถามเดียว (โดยไม่ใช้แบบสอบถามย่อย)


IordanTanev, Mark Brackett, RC - ขอบคุณสำหรับการตอบกลับมันเป็นการลองที่ดี แต่คุณต้องตรวจสอบสิ่งที่คุณทำก่อนโพสต์ไปที่ SO ข้อความค้นหาที่คุณระบุไม่เท่ากับข้อความค้นหาของฉัน คุณสามารถเห็นได้อย่างง่ายดายว่าฉันมีสเกลาร์ผลลัพธ์เสมอ แต่คิวรีของคุณส่งคืนหลายแถว
Novitzky

เพิ่งปรับปรุงคำถามเพื่อรวมความคิดเห็นที่ชัดเจนของคุณจากหนึ่งในคำตอบ
Jeff


นี่เป็นคำถามที่ดี ฉันสงสัยเช่นกันหากมีวิธีที่ง่ายกว่าในการทำเช่นนี้
Anupam

คำตอบ:


73

หากคุณกำลังพยายามปรับปรุงประสิทธิภาพคุณสามารถลองสร้างคอลัมน์ที่คำนวณแล้วยังคงอยู่บนค่าแฮชหรือค่าตัดแบ่งของสองคอลัมน์

เมื่อได้รับการยืนยันแล้วคอลัมน์จะถูกกำหนดไว้และคุณกำลังใช้การตั้งค่าฐานข้อมูล "sane" มันสามารถสร้างดัชนีได้และ / หรือสามารถสร้างสถิติได้

ฉันเชื่อว่าจำนวนคอลัมน์ที่คำนวณอย่างชัดเจนจะเทียบเท่ากับข้อความค้นหาของคุณ


4
ข้อเสนอแนะที่ยอดเยี่ยม! ยิ่งฉันอ่านมากเท่าไหร่ฉันก็ยิ่งรู้ตัวว่า SQL ไม่ค่อยมีความรู้เกี่ยวกับไวยากรณ์และฟังก์ชั่นและอีกมากมายเกี่ยวกับการใช้ตรรกะที่บริสุทธิ์ .. ฉันหวังว่าฉันจะมี 2 upvotes!
tumchaaditya

คำแนะนำที่ดีเกินไป มันหลีกเลี่ยงให้ฉันเขียนโค้ดที่ไม่จำเป็นลงไป
Avrajit Roy

1
คุณช่วยเพิ่มตัวอย่างหรือตัวอย่างโค้ดเพื่อแสดงเพิ่มเติมเกี่ยวกับความหมายของมันและวิธีการได้ไหม
jayqui

52

แก้ไข: แก้ไขจากแบบสอบถามการตรวจสอบอย่างเดียวที่เชื่อถือได้น้อยกว่า ฉันได้ค้นพบวิธีการทำเช่นนี้ (ใน SQL Server 2005) ที่ใช้งานได้ดีสำหรับฉันและฉันสามารถใช้คอลัมน์ได้มากเท่าที่ต้องการ (โดยเพิ่มลงใน ฟังก์ชัน CHECKSUM () ฟังก์ชัน REVERSE () เปลี่ยน ints ให้กลายเป็น varchars เพื่อสร้างความน่าเชื่อถือที่ชัดเจนยิ่งขึ้น

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

1
+1 Nice one ใช้งานได้ดี (เมื่อคุณมีประเภทคอลัมน์ที่ถูกต้องเพื่อดำเนินการ CheckSum บน ... ;)
Bernoulli IT

8
ด้วยแฮชเช่น Checksum () มีโอกาสเล็กน้อยที่แฮชเดียวกันจะถูกส่งคืนสำหรับอินพุตที่แตกต่างกันดังนั้นการนับอาจจะปิดเล็กน้อย HashBytes () เป็นโอกาสที่น้อยลง แต่ก็ยังไม่เป็นศูนย์ หากทั้งสองรหัสนั้นเป็นของ int (32b) ดังนั้น "แฮ็กแบบไม่สูญเสีย" สามารถรวมพวกมันเข้าด้วยกัน (64b) เช่น Id1 << 32 + Id2
crokusek

1
โอกาสไม่เล็กมากโดยเฉพาะอย่างยิ่งเมื่อคุณเริ่มรวมคอลัมน์ (ซึ่งเป็นสิ่งที่ควรจะหมายถึง) ฉันสงสัยเกี่ยวกับวิธีการนี้และในบางกรณีเช็คซัมก็จบลงด้วยจำนวนที่น้อยกว่า 10% หากคุณคิดว่ามันจะยาวไปสักหน่อย Checksum จะส่งกลับค่า int ดังนั้นหากคุณตรวจสอบช่วงใหญ่ ๆ แบบเต็มคุณจะได้จำนวนที่น้อยกว่าจริงประมาณ 2 พันล้านเท่า -1
pvolders

อัปเดตแบบสอบถามเพื่อรวมการใช้ "REVERSE" เพื่อลบโอกาสในการซ้ำซ้อน
JayTee

4
เราสามารถหลีกเลี่ยงการเช็ค - เราสามารถเชื่อมค่าสองค่าเข้าด้วยกันได้ไหม? ฉันคิดว่าความเสี่ยงนั้นพิจารณาเช่นเดียวกัน: ('เขา', 'ศิลปะ') == 'ได้ยิน', 't') แต่ฉันคิดว่าสามารถแก้ไขได้ด้วยตัวคั่นตามที่ @APC เสนอ (ค่าบางอย่างที่ไม่ปรากฏในคอลัมน์ใดคอลัมน์หนึ่ง) ดังนั้น 'เขา | ศิลปะ'! = 'ได้ยิน | 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

20

หากต้องการเรียกใช้เป็นแบบสอบถามเดียวเชื่อมคอลัมน์จากนั้นรับจำนวนอินสแตนซ์ของสตริงที่ต่อกัน

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/4955425และstackoverflow.com/a/1471713/4955425
sstan

1
FWIW เกือบจะใช้งานได้ใน PostgreSQL เพียงแค่ต้องวงเล็บพิเศษ:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph

14

เกี่ยวกับสิ่งที่ชอบ:

เลือกจำนวน (*)
จาก
  (เลือกจำนวน (*) cnt
   จาก DocumentOutputItems
   จัดกลุ่มตาม DocumentId, DocumentSessionId) t1

อาจเป็นเช่นเดียวกับที่คุณเป็นอยู่แล้ว แต่ก็หลีกเลี่ยงการแตก


ในการทดสอบของฉัน (โดยใช้ SET SHOWPLAN_ALL ON) มีแผนการดำเนินการเดียวกันและ TotalSubtreeCost
KM ที่แน่นอน

1
ขึ้นอยู่กับความซับซ้อนของการสืบค้นดั้งเดิมการแก้ปัญหาด้วยGROUP BYอาจแนะนำความท้าทายเพิ่มเติมอีกสองสามข้อสำหรับการแปลงการสืบค้นเพื่อให้ได้ผลลัพธ์ที่ต้องการ (เช่นเมื่อแบบสอบถามดั้งเดิมมีอยู่GROUP BYหรือมีHAVINGคำสั่ง ... )
Lukas Eder

8

นี่คือรุ่นที่สั้นกว่าโดยไม่ต้องเลือกย่อย:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

มันทำงานได้ดีใน MySQL และฉันคิดว่าเครื่องมือเพิ่มประสิทธิภาพมีเวลาที่เข้าใจได้ง่ายขึ้น

แก้ไข: เห็นได้ชัดว่าฉันผิด MSSQL และ MySQL - ขออภัยเกี่ยวกับเรื่องนี้ แต่อาจช่วยได้


6
ใน SQL Server คุณจะได้รับ: ข่าวสารเกี่ยวกับ 102, ระดับ 15, สถานะ 1, ไวยากรณ์ 1 บรรทัดที่ไม่ถูกต้องใกล้ ','
กม.

นี่คือสิ่งที่ฉันคิด ฉันต้องการทำสิ่งที่คล้ายกันใน MSSQL ถ้าเป็นไปได้
Novitzky

@ Kamil Nowicki ใน SQL Server คุณสามารถมีเพียงหนึ่งฟิลด์ใน COUNT () ในคำตอบของฉันฉันแสดงให้เห็นว่าคุณสามารถเชื่อมสองฟิลด์เป็นหนึ่งเดียวและลองวิธีนี้ อย่างไรก็ตามฉันแค่ติดกับต้นฉบับเนื่องจากแผนแบบสอบถามจะสิ้นสุดลงเหมือนเดิม
กม.

1
โปรดดูในคำตอบ @JayTee มันทำงานเหมือนจับใจ count ( distinct CHECKSUM ([Field1], [Field2])
กุสโตดิโอ

5

ฐานข้อมูล SQL จำนวนมาก (ส่วนใหญ่) สามารถทำงานกับ tuples เช่นค่าดังนั้นคุณสามารถทำได้: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; หากฐานข้อมูลของคุณไม่รองรับสิ่งนี้มันสามารถจำลองตามคำแนะนำ @ @ oncel-umut-turer ของ CHECKSUM หรือฟังก์ชัน scalar อื่น ๆ ที่มีเอกลักษณ์ที่ดี 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

@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 (... ) อีกอัน โดยพื้นฐานแล้วคำตอบนี้ให้คุณอีกวิธีในการแสดงรายการค่าที่แตกต่างที่คุณต้องการนับ มันไม่ได้ดีไปกว่าโซลูชันดั้งเดิมของคุณ
เดฟคอส

ขอบคุณเดฟ ฉันรู้ว่าคุณสามารถใช้กลุ่มโดยแทนความชัดเจนในกรณีของฉัน ฉันสงสัยว่าคุณจะได้รับผลลัพธ์สุดท้ายโดยใช้เพียงหนึ่งแบบสอบถาม ฉันคิดว่าเป็นไปไม่ได้ แต่ฉันอาจจะผิด
Novitzky

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 จากฉัน ขอบคุณ แต่ฉันจะติดกับแบบสอบถามของฉันตามที่คุณแนะนำ การใช้ "แปลง" สามารถลดประสิทธิภาพได้มากกว่าเดิม
Novitzky

2

ฉันพบสิ่งนี้เมื่อฉัน Googled สำหรับปัญหาของฉันเองพบว่าถ้าคุณนับวัตถุ DISTINCT คุณจะได้รับหมายเลขที่ถูกต้องที่ส่งคืน (ฉันใช้ MySQL)

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

5
ข้อความค้นหาด้านบนจะส่งคืนชุดผลลัพธ์ที่แตกต่างจากสิ่งที่ OP มองหา ( ชุดค่าผสมที่แตกต่างกันของDocumentIdและDocumentSessionId) Alexander Kjällโพสต์คำตอบที่ถูกต้องแล้วถ้า OP ใช้ MySQL และไม่ใช่ MS SQL Server
Anthony Geoghegan

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))

แต่นี่ก็ไม่ได้เป็นการเผชิญหน้ากัน

คุณสามารถใช้ HASHBYTES () ฟังก์ชั่นตามที่แนะนำในTSQL CHECKSUM ปริศนา อย่างไรก็ตามนี่ยังมีโอกาสเล็กน้อยที่จะไม่แสดงผลลัพธ์ที่ไม่ซ้ำ

ฉันอยากจะแนะนำให้ใช้

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

ไม่สนใจความซับซ้อนของสิ่งนี้ฉันรู้ว่าฉันไม่สามารถรับค่าของ 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ลองทำสิ่งนี้ได้อย่างง่ายดายและรวดเร็วมาก สมมติDocumentIdและDocumentSessionIdมีทั้งintS, และดังนั้นจึงยาว 4 ไบต์ ...

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

ปัญหาเฉพาะของฉันต้องการให้ฉันหารSUMด้วยการCOUNTรวมกันของคีย์ต่างประเทศและเขตข้อมูลวันที่ที่แตกต่างกันจัดกลุ่มโดยคีย์ต่างประเทศอื่นและบางครั้งกรองตามค่าหรือคีย์บางอย่าง ตารางมีขนาดใหญ่มากและการใช้แบบสอบถามย่อยเพิ่มเวลาแบบสอบถามอย่างมาก และเนื่องจากความซับซ้อนสถิติก็ไม่ได้เป็นตัวเลือกที่ทำงานได้ CHECKSUMวิธีการแก้ปัญหาก็ยังห่างไกลช้าเกินไปในการแปลงโดยเฉพาะอย่างยิ่งเป็นผลมาจากชนิดข้อมูลต่าง ๆ และผมก็ไม่อาจมีความเสี่ยงไม่สามารถไว้ใจได้ของ

อย่างไรก็ตามการใช้โซลูชันข้างต้นแทบไม่เพิ่มเวลาในการสืบค้น (เทียบกับการใช้เพียงแค่SUM) และควรเชื่อถือได้อย่างสมบูรณ์! ควรช่วยผู้อื่นในสถานการณ์ที่คล้ายคลึงกันดังนั้นฉันโพสต์ไว้ที่นี่


-1

คุณสามารถใช้ Count Function Twice

ในกรณีนี้มันจะเป็น:

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

สิ่งนี้ไม่ได้เป็นไปตามที่กำหนดในคำถามมันนับความแตกต่างในแต่ละคอลัมน์
naviram

-1

รหัสนี้ใช้พารามิเตอร์ที่แตกต่างกัน 2 รายการและแสดงจำนวนแถวที่เฉพาะเจาะจงสำหรับการนับแถวค่าที่แตกต่างเหล่านั้น มันทำงานให้ฉันใน MySQL เหมือนมีเสน่ห์

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.