Conteggio DISTINCT su più colonne


214

Esiste un modo migliore di eseguire una query come questa:

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

Devo contare il numero di elementi distinti da questa tabella, ma il distinto si trova su due colonne.

La mia query funziona bene, ma mi chiedevo se posso ottenere il risultato finale usando solo una query (senza usare una query secondaria)


IordanTanev, Mark Brackett, RC - grazie per le risposte, è stato un bel tentativo, ma devi controllare cosa stai facendo prima di pubblicare su SO. Le query che hai fornito non equivalgono alla mia query. Puoi facilmente vedere che ho sempre un risultato scalare, ma la tua query restituisce più righe.
Novitzky,

Ho appena aggiornato la domanda per includere il tuo commento chiarificatore da una delle risposte
Jeff


Questa è una buona domanda Mi chiedevo anche se ci fosse un modo più semplice per farlo
Anupam

Risposte:


73

Se si sta tentando di migliorare le prestazioni, è possibile provare a creare una colonna calcolata persistente su un hash o su un valore concatenato delle due colonne.

Una volta persistente, purché la colonna sia deterministica e si utilizzino impostazioni di database "normali", può essere indicizzata e / o è possibile creare statistiche su di essa.

Credo che un conteggio distinto della colonna calcolata sarebbe equivalente alla tua query.


4
Suggerimento eccellente! Più leggo, più mi rendo conto che SQL è meno sulla conoscenza della sintassi e delle funzioni e più sull'applicazione della logica pura. Vorrei avere 2 voti positivi!
tumchaaditya,

Suggerimento troppo buono. Mi ha evitato di scrivere codice non necessario a questo.
Avrajit Roy,

1
Vorresti aggiungere un esempio o un esempio di codice per mostrare di più su ciò che significa e su come farlo?
jayqui,

52

Modifica: modificato dalla query di checksum non affidabile che ho scoperto un modo per farlo (in SQL Server 2005) che funziona abbastanza bene per me e posso usare tutte le colonne di cui ho bisogno (aggiungendole a la funzione CHECKSUM ()). La funzione REVERSE () trasforma gli in in varchar per rendere il distinto più affidabile

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

1
+1 Bello, funziona perfettamente (quando hai i tipi di colonna giusti per eseguire un checksum su ...;)
Bernoulli IT

8
Con hash come Checksum (), ci sono poche possibilità che lo stesso hash venga restituito per input diversi, quindi il conteggio potrebbe essere leggermente off. HashBytes () è una possibilità ancora più piccola ma ancora non zero. Se quei due ID fossero int (32b), un "hash lossless" potrebbe combinarli in un bigint (64b) come Id1 << 32 + Id2.
crokusek,

1
la possibilità non è così piccola, specialmente quando inizi a combinare le colonne (che è ciò che doveva essere pensato per). Ero curioso di questo approccio e in un caso particolare il checksum è finito con un conteggio inferiore del 10%. Se ci pensi un po 'più a lungo, Checksum restituisce solo un int, quindi se avessi un checksum su un intervallo di bigint completo, finirai con un conteggio distinto circa 2 miliardi di volte più piccolo di quello che effettivamente c'è. -1
pvolders,

Aggiornamento della query per includere l'uso di "INVERSIONE" per rimuovere la possibilità di duplicati
JayTee

4
Potremmo evitare CHECKSUM - potremmo semplicemente concatenare i due valori insieme? Suppongo che rischi di considerare la stessa cosa: ('lui', 'arte') == 'ascolta', 't'). Ma penso che possa essere risolto con un delimitatore come propone @APC (un valore che non appare in nessuna delle colonne), quindi 'he | ​​art'! = 'Hear | t' Ci sono altri problemi con una semplice "concatenazione" approccio?
The Red Pea,

32

Cosa non ti piace della tua query esistente? Se sei preoccupato che DISTINCTattraverso due colonne non restituisca solo le permutazioni uniche, perché non provarlo?

Funziona certamente come ci si potrebbe aspettare da 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>

modificare

Sono andato in un vicolo cieco con analisi ma la risposta era deprimentemente ovvia ...

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

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

SQL>

modifica 2

Dati i seguenti dati, la soluzione concatenante fornita sopra non conterà:

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

Quindi includiamo un separatore ...

select col1 + '*' + col2 from t23
/

Ovviamente il separatore scelto deve essere un carattere, o un insieme di caratteri, che non può mai apparire in nessuna delle colonne.


+1 da me. Grazie per la tua risposta. La mia query funziona benissimo ma mi chiedevo se posso ottenere il risultato finale usando una sola query (senza usare una subquery)
Novitzky

20

Per eseguire come una singola query, concatenare le colonne, quindi ottenere il conteggio distinto di istanze della stringa concatenata.

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

In MySQL puoi fare la stessa cosa senza il passo di concatenazione come segue:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Questa funzionalità è menzionata nella documentazione di MySQL:

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


Questa era una domanda di SQL Server ed entrambe le opzioni che hai pubblicato sono già state menzionate nelle seguenti risposte a questa domanda: stackoverflow.com/a/1471444/4955425 e stackoverflow.com/a/1471713/4955425 .
sstan,

1
FWIW, questo funziona quasi in PostgreSQL; SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ho

14

Che ne dici di qualcosa come:

seleziona conteggio (*)
a partire dal
  (seleziona count (*) cnt
   da DocumentOutputItems
   raggruppa per DocumentId, DocumentSessionId) t1

Probabilmente fa lo stesso che sei già, ma evita il DISTINCT.


nei miei test (usando SET SHOWPLAN_ALL ON), aveva lo stesso piano di esecuzione e lo stesso TotalSubtreeCost
KM.

1
A seconda della complessità della query originale, risolvere questo problema GROUP BYpuò comportare un paio di ulteriori sfide alla trasformazione della query per ottenere l'output desiderato (ad es. Quando la query originale aveva già GROUP BYo HAVINGclausole ...)
Lukas Eder

8

Ecco una versione più breve senza la sottoselezione:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

Funziona bene in MySQL e penso che l'ottimizzatore abbia più tempo a comprenderlo.

Modifica: Apparentemente ho letto male MSSQL e MySQL - mi dispiace, ma forse aiuta comunque.


6
in SQL Server ottieni: Messaggio 102, Livello 15, Stato 1, Riga 1 Sintassi errata vicino a ",".
KM.

Questo è quello a cui stavo pensando. Voglio fare cose simili in MSSQL, se possibile.
Novitzky,

@Kamil Nowicki, in SQL Server, puoi avere solo un campo in un COUNT (), nella mia risposta mostro che puoi concatenare i due campi in uno e provare questo approccio. Tuttavia, resterei fedele all'originale poiché i piani di query sarebbero finiti allo stesso modo.
KM.

1
Dai un'occhiata nella risposta @JayTee. Esso funziona magicamente. count ( distinct CHECKSUM ([Field1], [Field2])
Custodio,

5

Molti (la maggior parte?) Database SQL possono funzionare con tuple come i valori, quindi puoi semplicemente farlo: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; se il tuo database non lo supporta, può essere simulato secondo il suggerimento di @ oncel-umut-turer di CHECKSUM o altre funzioni scalari che forniscono una buona unicità es COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Un uso correlato delle tuple sta eseguendo INquery come: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));


quali database supportano select count(distinct(a, b))? : D
Vytenis Bivainis,

@VytenisBivainis Conosco PostgreSQL, non so da quale versione.
Karmakaze,

3

Non c'è niente di sbagliato nella tua query, ma puoi anche farlo in questo modo:

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

3

Spero che funzioni, sto scrivendo su prima vista

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

7
Affinché ciò fornisca la risposta finale, dovresti racchiuderlo in un altro SELECT COUNT (*) FROM (...). Fondamentalmente questa risposta ti sta solo dando un altro modo per elencare i valori distinti che vuoi contare. Non è meglio della tua soluzione originale.
Dave Costa,

Grazie Dave. So che puoi usare group invece che distinto nel mio caso. Mi chiedevo se ottieni il risultato finale usando una sola query. Penso che sia impossibile ma potrei sbagliarmi.
Novitzky,

3

Ho usato questo approccio e ha funzionato per me.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

Nel mio caso, fornisce risultati corretti.


Non ti dà il conteggio di valori distinti congiuntamente a due colonne. Almeno non in MySQL 5.8.
Anwar Shaikh,

Questa domanda è taggata con SQL Server e questa non è sintassi di SQL Server
Tab Alleman

2

se avessi un solo campo su "DISTINCT", potresti usare:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

e che restituisce lo stesso piano di query dell'originale, come testato con SET SHOWPLAN_ALL ON. Tuttavia stai utilizzando due campi in modo da poter provare qualcosa di folle come:

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

ma avrai problemi se sono coinvolti NULL. Mi limiterei a seguire la query originale.


+1 da me. Grazie ma seguirò la mia domanda come mi hai suggerito. L'uso di "convert" può ridurre ulteriormente le prestazioni.
Novitzky,

2

Ho trovato questo quando ho cercato su Google il mio problema, ho scoperto che se si contano gli oggetti DISTINCT, si ottiene il numero corretto restituito (sto usando MySQL)

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

5
La query sopra restituirà un set di risultati diverso da quello che l'OP stava cercando (le distinte combinazioni di DocumentIde DocumentSessionId). Alexander Kjäll ha già pubblicato la risposta corretta se l'OP utilizzava MySQL e non MS SQL Server.
Anthony Geoghegan,

1

Vorrei che MS SQL potesse anche fare qualcosa come COUNT (DISTINCT A, B). Ma non può.

Inizialmente la risposta di JayTee mi è sembrata una soluzione dopo alcuni test CHECKSUM () non è riuscito a creare valori univoci. Un rapido esempio è che sia CHECKSUM (31.467.519) sia CHECKSUM (69,1120,823) danno la stessa risposta che è 55.

Quindi ho fatto alcune ricerche e ho scoperto che Microsoft NON consiglia di utilizzare CHECKSUM per scopi di rilevamento delle modifiche. In alcuni forum alcuni hanno suggerito di utilizzare

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

ma anche questo non è confortante.

È possibile utilizzare la funzione HASHBYTES () come suggerito nell'enigma di TSQL CHECKSUM . Tuttavia, ciò ha anche una piccola possibilità di non restituire risultati unici.

Suggerirei di usare

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

1

Cosa ne pensi di questo,

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

Questo ci consentirà di contare tutte le possibili combinazioni di DocumentId e DocumentSessionId


0

Per me funziona. In oracolo:

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;

0

Avevo una domanda simile ma la query che avevo era una sottoquery con i dati di confronto nella query principale. qualcosa di simile a:

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

ignorando la complessità di questo, mi sono reso conto che non potevo ottenere il valore di a.code nella sottoquery con la doppia sub query descritta nella domanda originale

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

Quindi alla fine ho capito che avrei potuto imbrogliare e combinare le colonne:

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

Questo è ciò che ha finito per funzionare


0

Se stai lavorando con tipi di dati di lunghezza fissa, puoi eseguire il cast binaryper farlo molto facilmente e molto rapidamente. Supponendo DocumentIdche DocumentSessionIdsiano entrambi ints, e pertanto siano lunghi 4 byte ...

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

Il mio problema specifico mi ha richiesto di dividere a SUMper la COUNTcombinazione distinta di varie chiavi esterne e un campo data, raggruppando per un'altra chiave esterna e occasionalmente filtrando per determinati valori o chiavi. La tabella è molto grande e l'utilizzo di una query secondaria ha aumentato notevolmente il tempo di query. E a causa della complessità, le statistiche non erano semplicemente un'opzione praticabile. La CHECKSUMsoluzione era anche troppo lenta nella sua conversione, in particolare a causa dei vari tipi di dati, e non potevo rischiare la sua inaffidabilità.

Tuttavia, l'utilizzo della soluzione di cui sopra non ha praticamente avuto alcun aumento del tempo di query (rispetto all'utilizzo del solo SUM) e dovrebbe essere completamente affidabile! Dovrebbe essere in grado di aiutare gli altri in una situazione simile, quindi sto pubblicando qui.


-1

Puoi semplicemente usare la funzione Count due volte.

In questo caso, sarebbe:

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

questo non fa come richiesto nella domanda, conta il distinto in separato per ogni colonna
naviram

-1

Questo codice utilizza parametri distinti su 2 e fornisce il conteggio del numero di righe specifiche per il conteggio delle righe di quei valori distinti. Ha funzionato per me in MySQL come un fascino.

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.