2009-09-24 20 views
133

Esiste un modo migliore di fare una query come questa:Contando DISTINCT su più colonne

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

ho bisogno di contare il numero di elementi distinti da questo tavolo, ma il distinto è su due colonne.

La mia domanda funziona bene, ma mi chiedevo se posso ottenere il risultato finale con una sola query (senza l'utilizzo di un sub-query)

+0

IordanTanev, Mark Brackett, RC - grazie per le risposte, è stato un bel tentativo, ma è necessario verificare cosa si sta facendo prima di postare in SO. Le query che hai fornito non sono equivalenti alla mia query. Puoi facilmente vedere che ho sempre un risultato scalare ma la tua query restituisce più righe. – Novitzky

+0

Appena aggiornata la domanda per includere il tuo commento chiarificatore da una delle risposte – Jeff

risposta

45

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

Una volta che è persistente, purché la colonna è deterministico e si sta utilizzando le impostazioni del database "sane", può essere indicizzato e/o statistica può essere creato su di essa.

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

+3

Eccellente suggerimento! Ho letto, più mi sto rendendo conto che SQL è meno sulla conoscenza della sintassi e delle funzioni e più sull'applicazione della logica pura .. Mi piacerebbe avere 2 voti positivi – tumchaaditya

+0

Troppo buon consiglio: mi ha evitato di scrivere codice non necessario a questo –

2

Spero che questo funziona sto scrivendo sulla prima vista

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

Per poter dare la risposta finale, dovresti avvolgerla in un altro SELECT COUNT (*) FROM (...). Essenzialmente questa risposta ti sta solo dando un altro modo per elencare i valori distinti che vuoi contare. Non è migliore della tua soluzione originale. –

+0

Grazie Dave. So che puoi usare il gruppo anziché il distinto nel mio caso. Mi stavo chiedendo se ottieni il risultato finale usando una sola query. Penso che sia impossibile ma potrei sbagliarmi. – Novitzky

16

Che cos'è della tua query esistente che non ti piace? Se sei preoccupato che lo DISTINCT attraverso due colonne non restituisca solo le permutazioni uniche perché non provarlo?

Certamente funziona come ci si potrebbe aspettare in 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 strumenti di analisi, ma la risposta è stata tristemente ovvio ...

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

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

SQL> 

Edit 2

Dato i seguenti dati la soluzione di concatenamento sopra riportata avrà un conteggio errato:

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

Così abbiamo per includere un separatore ...

select col1 + '*' + col2 from t23 
/

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

+0

+1 da parte mia. Grazie per la tua risposta. La mia query funziona bene, ma mi chiedevo se posso ottenere il risultato finale utilizzando una sola query (senza utilizzare una subquery) – Novitzky

11

ne dite qualcosa di simile:

 
select count(*) 
from 
    (select count(*) cnt 
    from DocumentOutputItems 
    group by DocumentId, DocumentSessionId) t1 

Probabilmente fa proprio lo stesso che si è già però, ma evita il DISTINCT.

+0

Sì, hai ragione. Fa lo stesso lavoro del mio originale. – Novitzky

+0

nei miei test (utilizzando SET SHOWPLAN_ALL ON), aveva lo stesso piano di esecuzione e lo stesso TotalSubtreeCost –

+0

+1 per un bel tentativo e la spiegazione. – Novitzky

6

Ecco una versione più corta senza la subselect:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems 

Funziona bene in MySQL, e penso che l'ottimizzatore ha un tempo più facile capire questo.

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

+5

in SQL Server si ottiene: __Msg 102, livello 15, stato 1, riga 1 sintassi non corretta vicino a ',' .__ –

+0

Questo è ciò a cui stavo pensando. Voglio fare cose simili in MSSQL se possibile. – Novitzky

+0

@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 solo e provare questo approccio. Tuttavia, mi limiterei a seguire l'originale poiché i piani di query finirebbero per essere gli stessi. –

2

se si ha un solo campo per "DISTINCT", è possibile utilizzare:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems 

e che fa tornare lo stesso piano di query come l'originale, come testato con SET SHOWPLAN_ALL ON. Tuttavia si sta utilizzando due campi così si potrebbe provare qualcosa di pazzo come:

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

ma avrete problemi se i NULL sono coinvolti.Vorrei solo attenermi alla query originale.

+0

+1 da parte mia Grazie, ma seguirò la mia richiesta come suggerito.Utilizzare "convert" può ridurre ulteriormente le prestazioni – Novitzky

3

Non c'è niente di sbagliato con la tua ricerca, ma si potrebbe anche fare in questo modo:

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

Edit: Alterato dal meno-che-affidabile checksum di sola interrogazione ho scoperto un modo per fare questo (in SQL Server 2005), che funziona piuttosto bene per me e posso usare un numero di colonne Ho bisogno (aggiungendoli alla funzione CHECKSUM()). La funzione REVERSE() trasforma gli INT in VARCHAR per rendere la distinta più affidabile

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

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

+7

Con hash come Checksum(), ci sono poche probabilità che lo stesso hash venga restituito per diversi input in modo che il conteggio possa essere leggermente inferiore. HashBytes() è una possibilità ancora più piccola ma non ancora zero. Se questi due Id erano int (32b), allora un "hash senza perdita" poteva 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ò per cui doveva essere pensato). Ero curioso di questo approccio e in un caso particolare il risultato è risultato inferiore del 10%. Se ci pensi un po 'più a lungo, Checksum restituisce solo un int, quindi se esegui il checksum su un range completo di bigint ti ritrovi con un conteggio distinto di circa 2 miliardi di volte inferiore a quello che effettivamente è. -1 – pvolders

1

Vorrei MS SQL potrebbe anche fare qualcosa di simile COUNT (DISTINCT A, B). Ma non può.

In risposta prima di JayTee sembrava una soluzione per me bu dopo qualche 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 delle ricerche e ho scoperto che Microsoft NON consiglia l'uso di CHECKSUM per il rilevamento dei cambiamenti. 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 in TSQL CHECKSUM conundrum. Tuttavia questo ha anche una piccola possibilità di non restituire risultati unici.

Io suggerirei di usare

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

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

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

La query sopra riportata restituirà un insieme diverso di risultati rispetto a ciò che l'OP stava cercando (le distinte ** combinazioni ** di 'DocumentId' e ' DocumentSessionId'). Alexander Kjäll ha già pubblicato la risposta corretta se l'OP stava usando MySQL e non MS SQL Server. –

4

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

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

In MySQL è possibile fare la stessa cosa senza il passaggio di concatenazione come segue:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems; 

Questa funzione è menzionato nella documentazione di MySQL:

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

+0

Questa era una domanda di SQL Server e entrambe le opzioni che hai postato sono già state citate nelle seguenti risposte a questa domanda: http://stackoverflow.com/a/1471444/4955425 e http://stackoverflow.com/a/1471713/4.955.425. – sstan

-2

Questa è stata posta e ha risposto su Quora (https://www.quora.com/In-SQL-how-to-I-count-DISTINCT-over-multiple-columns):

select col1, col2, col3, count(*) 
from table 
group by col1, col2, col3 

Stavo lavorando su questo in SAS, e SAS Proc SQL non mi piace DISTINCT con più di una colonna.

+0

La query originale nella domanda restituisce il numero di combinazioni in colonne date. Questa risposta restituisce invece il numero di occorrenze per ciascuna combinazione in colonne date. – jumxozizi

-2

Questa seguito interrogazione ha lavorato per me su MySQL:

SELECT COUNT(DISTINCT col_1,col_2,..) from table_name; 

Le colonne indicate al precedente cioè query. col_1, col_2 ha un vincolo combinato UNIQUE su di essi. Ciò significa che nella mia tabella table_name, ho creato un indice UNIQUE su col_1 + col_2.