DISTINCT über mehrere Spalten zählen

212

Gibt es eine bessere Möglichkeit, eine Abfrage wie diese durchzuführen:

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

Ich muss die Anzahl der verschiedenen Elemente aus dieser Tabelle zählen, aber das Unterschied ist über zwei Spalten.

Meine Abfrage funktioniert einwandfrei, aber ich habe mich gefragt, ob ich mit nur einer Abfrage (ohne Verwendung einer Unterabfrage) das Endergebnis erzielen kann.

Novitzky
quelle
IordanTanev, Mark Brackett, RC - danke für die Antworten, es war ein schöner Versuch, aber Sie müssen überprüfen, was Sie tun, bevor Sie auf SO posten. Die von Ihnen angegebenen Abfragen entsprechen nicht meiner Abfrage. Sie können leicht erkennen, dass ich immer ein skalares Ergebnis habe, aber Ihre Abfrage gibt mehrere Zeilen zurück.
Novitzky
Ich habe gerade die Frage aktualisiert, um Ihren klarstellenden Kommentar aus einer der Antworten aufzunehmen
Jeff
Zur Info: community.oracle.com/ideas/18664
quetzalcoatl
Das ist eine gute Frage. Ich habe mich auch gefragt, ob es einen einfacheren Weg gibt
Anupam

Antworten:

73

Wenn Sie versuchen, die Leistung zu verbessern, können Sie versuchen, eine persistierte berechnete Spalte entweder mit einem Hash oder einem verketteten Wert der beiden Spalten zu erstellen.

Sobald es beibehalten wird, kann die Spalte indiziert und / oder Statistiken erstellt werden, sofern die Spalte deterministisch ist und Sie "gesunde" Datenbankeinstellungen verwenden.

Ich glaube, eine eindeutige Anzahl der berechneten Spalten würde Ihrer Abfrage entsprechen.

Jason Horner
quelle
4
Hervorragender Vorschlag! Je mehr ich lese, desto mehr wird mir klar, dass es in SQL weniger darum geht, Syntax und Funktionen zu kennen, als vielmehr darum, reine Logik anzuwenden. Ich wünschte, ich hätte zwei positive Stimmen!
Tumchaaditya
Zu guter Vorschlag. Es hat mich vermieden, unnötigen Code dazu zu schreiben.
Avrajit Roy
1
Würden Sie bitte ein Beispiel oder ein Codebeispiel hinzufügen, um mehr darüber zu zeigen, was dies bedeutet und wie es geht?
Jayqui
52

Bearbeiten: Geändert von der weniger zuverlässigen Nur-Prüfsummen-Abfrage habe ich einen Weg gefunden, dies zu tun (in SQL Server 2005), der für mich ziemlich gut funktioniert, und ich kann so viele Spalten verwenden, wie ich brauche (indem ich sie hinzufüge) die CHECKSUM () Funktion). Die REVERSE () -Funktion wandelt die Ints in Varchars um, um die Unterscheidung zuverlässiger zu machen

SELECT COUNT(DISTINCT (CHECKSUM(DocumentId,DocumentSessionId)) + CHECKSUM(REVERSE(DocumentId),REVERSE(DocumentSessionId)) )
FROM DocumentOutPutItems
JayTee
quelle
1
+1 Schön, funktioniert perfekt (wenn Sie die richtigen Spaltentypen haben, um eine CheckSum durchzuführen an ...;)
Bernoulli IT
8
Bei Hashes wie Checksum () besteht eine geringe Wahrscheinlichkeit, dass derselbe Hash für verschiedene Eingaben zurückgegeben wird, sodass die Anzahl möglicherweise geringfügig abweicht. HashBytes () ist eine noch kleinere Chance, aber immer noch nicht Null. Wenn diese beiden Ids int's (32b) wären, könnte ein "verlustfreier Hash" sie zu einem Bigint (64b) wie Id1 << 32 + Id2 kombinieren.
Crokusek
1
Die Chance ist nicht so gering, besonders wenn Sie anfangen, Spalten zu kombinieren (wofür es eigentlich gedacht war). Ich war neugierig auf diesen Ansatz und in einem bestimmten Fall war die Prüfsumme um 10% kleiner. Wenn Sie etwas länger darüber nachdenken, gibt Checksum nur ein int zurück. Wenn Sie also einen vollständigen Bigint-Bereich prüfen, erhalten Sie eine eindeutige Anzahl, die etwa 2 Milliarden Mal kleiner ist als tatsächlich. -1
pvolders
Die Abfrage wurde aktualisiert und enthält nun "REVERSE", um die Möglichkeit von Duplikaten zu beseitigen
JayTee,
4
Könnten wir CHECKSUM vermeiden - könnten wir die beiden Werte einfach miteinander verketten? Ich nehme an, dass das Risiko besteht, dasselbe zu betrachten: ('er', 'Kunst') == 'hören', 't'). Aber ich denke, das kann mit einem Trennzeichen gelöst werden, wie @APC vorschlägt (ein Wert, der in keiner der Spalten erscheint), also 'he | ​​art'! = 'Hear | t' Gibt es andere Probleme mit einer einfachen "Verkettung"? Ansatz?
Die rote Erbse
31

Was gefällt Ihnen an Ihrer vorhandenen Abfrage nicht? Wenn Sie befürchten, dass DISTINCTüber zwei Spalten nicht nur die eindeutigen Permutationen zurückgegeben werden, probieren Sie es aus.

Es funktioniert auf jeden Fall so, wie Sie es von Oracle erwarten.

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>

bearbeiten

Ich ging mit Analytik eine Sackgasse entlang, aber die Antwort war bedrückend offensichtlich ...

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

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

SQL>

bearbeiten 2

Angesichts der folgenden Daten wird die oben bereitgestellte Verkettungslösung falsch gezählt:

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

Also müssen wir ein Trennzeichen einfügen ...

select col1 + '*' + col2 from t23
/

Offensichtlich muss das gewählte Trennzeichen ein Zeichen oder eine Reihe von Zeichen sein, die in keiner der Spalten erscheinen dürfen.

APC
quelle
+1 von mir. Danke für deine Antwort. Meine Abfrage funktioniert gut, aber ich habe mich gefragt, ob ich das Endergebnis mit nur einer Abfrage (ohne Verwendung einer Unterabfrage) erhalten kann
Novitzky
19

Um als einzelne Abfrage ausgeführt zu werden, verketten Sie die Spalten und ermitteln Sie dann die eindeutige Anzahl der Instanzen der verketteten Zeichenfolge.

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

In MySQL können Sie dasselbe ohne den Verkettungsschritt wie folgt tun:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Diese Funktion wird in der MySQL-Dokumentation erwähnt:

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

spelunk1
quelle
Dies war eine SQL Server-Frage, und beide von Ihnen veröffentlichten Optionen wurden bereits in den folgenden Antworten auf diese Frage erwähnt: stackoverflow.com/a/1471444/4955425 und stackoverflow.com/a/1471713/4955425 .
Sstan
1
FWIW, das funktioniert fast in PostgreSQL; brauche nur zusätzliche Klammern:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
Ijoseph
14

Wie wäre es mit so etwas wie:

Anzahl auswählen (*)
von
  (Wählen Sie count (*) cnt
   von DocumentOutputItems
   gruppieren nach DocumentId, DocumentSessionId) t1

Wahrscheinlich macht es genau das Gleiche wie Sie es bereits sind, aber es vermeidet das UNTERSCHEIDEN.

Trevor Tippins
quelle
In meinen Tests (mit SET SHOWPLAN_ALL ON) hatte es den gleichen Ausführungsplan und genau den gleichen TotalSubtreeCost
KM.
1
Abhängig von der Komplexität der ursprünglichen Abfrage kann die Lösung dieses GROUP BYProblems einige zusätzliche Herausforderungen für die Abfragetransformation mit sich bringen, um die gewünschte Ausgabe zu erzielen (z. B. wenn die ursprüngliche Abfrage bereits vorhanden war GROUP BYoder HAVINGKlauseln ...)
Lukas Eder
8

Hier ist eine kürzere Version ohne die Unterauswahl:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

Es funktioniert gut in MySQL, und ich denke, dass es dem Optimierer leichter fällt, dieses zu verstehen.

Edit: Anscheinend habe ich MSSQL und MySQL falsch verstanden - sorry, aber vielleicht hilft es trotzdem.

Alexander Kjäll
quelle
6
In SQL Server erhalten Sie: Nachricht 102, Ebene 15, Status 1, Zeile 1 Falsche Syntax in der Nähe von ','.
KM.
Daran habe ich gedacht. Ich möchte, wenn möglich, etwas Ähnliches in MSSQL tun.
Novitzky
@Kamil Nowicki, in SQL Server können Sie nur ein Feld in einem COUNT () haben. In meiner Antwort zeige ich, dass Sie die beiden Felder zu einem verketten und diesen Ansatz ausprobieren können. Ich würde mich jedoch nur an das Original halten, da die Abfragepläne am Ende gleich bleiben würden.
KM.
1
Bitte werfen Sie einen Blick in @ JayTee Antwort. Es wirkt wie ein Zauber. count ( distinct CHECKSUM ([Field1], [Field2])
Custodio
5

Viele (die meisten?) SQL-Datenbanken können mit Tupeln wie Werten arbeiten, sodass Sie einfach SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; Folgendes tun können: Wenn Ihre Datenbank dies nicht unterstützt, kann dies gemäß dem Vorschlag von @ oncel-umut-turer von CHECKSUM oder einer anderen Skalarfunktion simuliert werden, die eine gute Eindeutigkeit bietet zB COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Eine verwandte Verwendung von Tupeln ist das Ausführen von INAbfragen wie: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));

Karmakaze
quelle
Welche Datenbanken unterstützen select count(distinct(a, b))? : D
Vytenis Bivainis
@VytenisBivainis Ich weiß, dass PostgreSQL dies tut - nicht sicher, seit welcher Version.
Karmakaze
3

An Ihrer Abfrage ist nichts auszusetzen, aber Sie können dies auch folgendermaßen tun:

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

Hoffe das funktioniert ich schreibe auf prima vista

SELECT COUNT(*) 
FROM DocumentOutputItems 
GROUP BY DocumentId, DocumentSessionId
IordanTanev
quelle
7
Damit dies die endgültige Antwort gibt, müssten Sie es in einen anderen SELECT COUNT (*) FROM (...) einschließen. Im Wesentlichen gibt Ihnen diese Antwort nur eine andere Möglichkeit, die unterschiedlichen Werte aufzulisten, die Sie zählen möchten. Es ist nicht besser als Ihre ursprüngliche Lösung.
Dave Costa
Danke Dave. Ich weiß, dass Sie in meinem Fall group by anstelle von different verwenden können. Ich habe mich gefragt, ob Sie das Endergebnis mit nur einer Abfrage erhalten. Ich denke ist unmöglich, aber ich könnte mich irren.
Novitzky
3

Ich habe diesen Ansatz verwendet und es hat bei mir funktioniert.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

Für meinen Fall liefert es das richtige Ergebnis.

Jaanis Veinberg
quelle
Es gibt Ihnen nicht die Anzahl der unterschiedlichen Werte in Verbindung mit zwei Spalten. Zumindest nicht in MySQL 5.8.
Anwar Shaikh
Diese Frage ist mit SQL Server gekennzeichnet, und dies ist keine SQL Server-Syntax
Tab Alleman
2

Wenn Sie nur ein Feld für "DISTINCT" hätten, könnten Sie Folgendes verwenden:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

und das gibt den gleichen Abfrageplan wie das Original zurück, wie mit SET SHOWPLAN_ALL ON getestet. Sie verwenden jedoch zwei Felder, um etwas Verrücktes auszuprobieren:

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

Sie haben jedoch Probleme, wenn NULL-Werte beteiligt sind. Ich würde mich einfach an die ursprüngliche Abfrage halten.

KM.
quelle
+1 von mir. Vielen Dank, aber ich werde bei meiner Frage bleiben, wie Sie vorgeschlagen haben. Die Verwendung von "Konvertieren" kann die Leistung noch weiter verringern.
Novitzky
2

Ich habe dies gefunden, als ich nach meinem eigenen Problem gegoogelt habe und festgestellt habe, dass beim Zählen von DISTINCT-Objekten die richtige Nummer zurückgegeben wird (ich verwende MySQL).

SELECT COUNT(DISTINCT DocumentID) AS Count1, 
  COUNT(DISTINCT DocumentSessionId) AS Count2
  FROM DocumentOutputItems
tehaugmenter
quelle
5
Die obige Abfrage gibt eine andere Reihe von Ergebnissen zurück als das, wonach das OP gesucht hat (die unterschiedlichen Kombinationen von DocumentIdund DocumentSessionId). Alexander Kjäll hat bereits die richtige Antwort gepostet, wenn das OP MySQL und nicht MS SQL Server verwendet.
Anthony Geoghegan
1

Ich wünschte, MS SQL könnte auch so etwas wie COUNT (DISTINCT A, B) tun. Aber es kann nicht.

Zuerst schien mir JayTees Antwort eine Lösung zu sein, aber nach einigen Tests konnte CHECKSUM () keine eindeutigen Werte erstellen. Ein kurzes Beispiel ist, dass sowohl CHECKSUM (31.467.519) als auch CHECKSUM (69.1120.823) dieselbe Antwort geben, nämlich 55.

Dann habe ich einige Nachforschungen angestellt und festgestellt, dass Microsoft die Verwendung von CHECKSUM NICHT zur Änderungserkennung empfiehlt. In einigen Foren schlugen einige die Verwendung vor

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

das ist aber auch nicht tröstlich.

Sie können die Funktion HASHBYTES () verwenden, wie im TSQL CHECKSUM-Rätsel vorgeschlagen . Dies hat jedoch auch eine geringe Wahrscheinlichkeit, dass keine eindeutigen Ergebnisse zurückgegeben werden.

Ich würde vorschlagen, zu verwenden

SELECT COUNT(DISTINCT CAST(DocumentId AS VARCHAR)+'-'+CAST(DocumentSessionId AS VARCHAR)) FROM DocumentOutputItems
Oncel Umut TURER
quelle
1

Wie wäre es damit,

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

Dadurch erhalten wir die Anzahl aller möglichen Kombinationen von DocumentId und DocumentSessionId

Nikhil Singh
quelle
0

Für mich geht das. Im Orakel:

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

In jpql:

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

Ich hatte eine ähnliche Frage, aber die Abfrage, die ich hatte, war eine Unterabfrage mit den Vergleichsdaten in der Hauptabfrage. etwas wie:

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

Als ich die Komplexität ignorierte, stellte ich fest, dass ich den Wert von a.code mit der in der ursprünglichen Frage beschriebenen doppelten Unterabfrage nicht in die Unterabfrage aufnehmen konnte

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

Also fand ich schließlich heraus, dass ich schummeln und die Spalten kombinieren konnte:

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

Das hat letztendlich funktioniert

Mark Rogers
quelle
0

Wenn Sie mit Datentypen fester Länge arbeiten, können binarySie dies sehr einfach und sehr schnell tun. Angenommen DocumentIdund DocumentSessionIdsind beide ints und sind daher 4 Bytes lang ...

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

Mein spezifisches Problem erforderte, dass ich a SUMdurch die COUNTunterschiedliche Kombination verschiedener Fremdschlüssel und eines Datumsfelds dividierte, nach einem anderen Fremdschlüssel gruppierte und gelegentlich nach bestimmten Werten oder Schlüsseln filterte. Die Tabelle ist sehr groß, und die Verwendung einer Unterabfrage hat die Abfragezeit erheblich verlängert. Und aufgrund der Komplexität waren Statistiken einfach keine praktikable Option. DasCHECKSUM Konvertierung der Lösung war auch viel zu langsam, insbesondere aufgrund der verschiedenen Datentypen, und ich konnte ihre Unzuverlässigkeit nicht riskieren.

Die Verwendung der oben genannten Lösung hatte jedoch praktisch keine Verlängerung der Abfragezeit (im Vergleich zur Verwendung der einfachen Lösung SUM) und sollte absolut zuverlässig sein! Es sollte in der Lage sein, anderen in einer ähnlichen Situation zu helfen, also poste ich es hier.

IphStich
quelle
-1

Sie können die Zählfunktion einfach zweimal verwenden.

In diesem Fall wäre es:

SELECT COUNT (DISTINCT DocumentId), COUNT (DISTINCT DocumentSessionId) 
FROM DocumentOutputItems
Bibek
quelle
Dies entspricht nicht den
Anforderungen
-1

Dieser Code verwendet unterschiedliche Parameter für 2 und gibt die Anzahl der Zeilen an, die für diese unterschiedlichen Werte spezifisch sind. Es hat für mich in MySQL wie ein Zauber funktioniert.

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