Come ottenere più conteggi con una query SQL?


316

Mi chiedo come scrivere questa domanda.

So che questa sintassi reale è fasulla, ma ti aiuterà a capire cosa voglio. Ne ho bisogno in questo formato, perché fa parte di una query molto più grande.

SELECT distributor_id, 
COUNT(*) AS TOTAL, 
COUNT(*) WHERE level = 'exec', 
COUNT(*) WHERE level = 'personal'

Ho bisogno che tutto questo sia restituito in una query.

Inoltre, deve essere in una riga, quindi non funzionerà quanto segue:

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'

1
Questa tua domanda ha funzionato correttamente ?? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Pratik,

Risposte:


690

È possibile utilizzare CASEun'istruzione con una funzione aggregata. Questa è sostanzialmente la stessa cosa di una PIVOTfunzione in alcuni RDBMS:

SELECT distributor_id,
    count(*) AS total,
    sum(case when level = 'exec' then 1 else 0 end) AS ExecCount,
    sum(case when level = 'personal' then 1 else 0 end) AS PersonalCount
FROM yourtable
GROUP BY distributor_id

55
Fantastico, è fantastico. Bella risposta. Solo una nota per le persone che sono inciampate qui. Conteggio conterà tutte le righe, la somma farà la stessa cosa di un conteggio se utilizzata con un'istruzione case.
John Ballinger,

1
Soluzione geniale! Probabilmente vale la pena notare che questo metodo funziona altrettanto bene se si combinano molte tabelle insieme in una query, poiché l'utilizzo di query secondarie può diventare piuttosto complicato in quell'istanza.
Darren Crabb,

7
Grazie per questa soluzione molto elegante. A proposito, questo funziona anche con TSQL.
Annie Lagang,

6
Perché questa potrebbe non essere la risposta migliore: sempre una scansione completa della tabella. Prendi in considerazione un join di sottoquery conteggio o conteggi nidificati in una selezione. Tuttavia, senza indici presenti, questo potrebbe essere il migliore in quanto hai garantito solo una scansione della tabella rispetto a più. Vedi la risposta di @KevinBalmforth
YoYo

1
@JohnBallinger, 'Conteggio conterà tutte le righe' - COUNTconterà distributor_idsaggio. non tutte le righe della tabella, giusto?
Istiaque Ahmed,

88

Un modo che funziona di sicuro

SELECT a.distributor_id,
    (SELECT COUNT(*) FROM myTable WHERE level='personal' and distributor_id = a.distributor_id) as PersonalCount,
    (SELECT COUNT(*) FROM myTable WHERE level='exec' and distributor_id = a.distributor_id) as ExecCount,
    (SELECT COUNT(*) FROM myTable WHERE distributor_id = a.distributor_id) as TotalCount
FROM (SELECT DISTINCT distributor_id FROM myTable) a ;

EDIT:
vedi la suddivisione delle prestazioni di @ KevinBalmforth sul perché probabilmente non vuoi usare questo metodo e invece dovresti optare per la risposta di @ Taryn ♦. Lascio questo in modo che le persone possano capire le loro opzioni.


2
Questo mi ha aiutato a risolvere come eseguire più conteggi e generarli in una singola istruzione SELECT con ogni conteggio come una colonna. Funziona alla grande - grazie!
Mark

2
Sono stato in grado di utilizzare ciò che hai fornito qui, in un mio progetto. Ora tutto è in una singola query, anziché in più query. La pagina si carica in meno di un secondo, rispetto a 5-8 secondi con più query. Lo adoro. Grazie, Notme.
Wayne Barron,

1
Questo potrebbe funzionare bene se ogni sub query effettivamente colpisce un indice. In caso contrario, la sum(case...)soluzione dovrebbe essere considerata.
YoYo

1
Si noti che in alternativa al distinto, poiché ho apportato la correzione, è anche possibile / utilizzare meglio group bycon il vantaggio di sostituire un'intera query nidificata con una semplice count(*)come mostra @Mihai - con ulteriori semplificazioni della sintassi di MySQL.
YoYo

43
SELECT 
    distributor_id, 
    COUNT(*) AS TOTAL, 
    COUNT(IF(level='exec',1,null)),
    COUNT(IF(level='personal',1,null))
FROM sometable;

COUNTconta solo i non nullvalori e DECODErestituirà un valore non nullo 1solo se la condizione è soddisfatta.


quale distributor_idverrà mostrata la query? Mostra 1 riga in totale.
Istiaque Ahmed,

L'OP ha un gruppo sulla colonna che è stata omessa nella mia risposta.
Majid Laissi,

mi hai salvato la vita, tutte le altre domande restituiscono più righe in MySQL. Grazie mille
Abner

1
@Abner contento che questo aiuti ancora dopo 8 anni :)
Majid Laissi

@MajidLaissi sì, ha cambiato il mio tempo di query da un minuto a meno di un secondo. :)
Abner

25

Basandosi su altre risposte postate.

Entrambi produrranno i giusti valori:

select distributor_id,
    count(*) total,
    sum(case when level = 'exec' then 1 else 0 end) ExecCount,
    sum(case when level = 'personal' then 1 else 0 end) PersonalCount
from yourtable
group by distributor_id

SELECT a.distributor_id,
          (SELECT COUNT(*) FROM myTable WHERE level='personal' and distributor_id = a.distributor_id) as PersonalCount,
          (SELECT COUNT(*) FROM myTable WHERE level='exec' and distributor_id = a.distributor_id) as ExecCount,
          (SELECT COUNT(*) FROM myTable WHERE distributor_id = a.distributor_id) as TotalCount
       FROM myTable a ; 

Tuttavia, le prestazioni sono piuttosto diverse, il che sarà ovviamente più rilevante all'aumentare della quantità di dati.

Ho scoperto che, supponendo che non siano stati definiti indici sulla tabella, la query che utilizza SUM eseguirà una singola scansione della tabella, mentre la query con COUNT eseguirà più scansioni della tabella.

Ad esempio, esegui il seguente script:

IF OBJECT_ID (N't1', N'U') IS NOT NULL 
drop table t1

create table t1 (f1 int)


    insert into t1 values (1) 
    insert into t1 values (1) 
    insert into t1 values (2)
    insert into t1 values (2)
    insert into t1 values (2)
    insert into t1 values (3)
    insert into t1 values (3)
    insert into t1 values (3)
    insert into t1 values (3)
    insert into t1 values (4)
    insert into t1 values (4)
    insert into t1 values (4)
    insert into t1 values (4)
    insert into t1 values (4)


SELECT SUM(CASE WHEN f1 = 1 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 2 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 3 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 4 THEN 1 else 0 end)
from t1

SELECT 
(select COUNT(*) from t1 where f1 = 1),
(select COUNT(*) from t1 where f1 = 2),
(select COUNT(*) from t1 where f1 = 3),
(select COUNT(*) from t1 where f1 = 4)

Evidenziare le 2 istruzioni SELECT e fare clic sull'icona Visualizza piano di esecuzione stimato. Vedrai che la prima istruzione eseguirà una scansione della tabella e la seconda farà 4. Ovviamente una scansione della tabella è migliore di 4.

Anche l'aggiunta di un indice cluster è interessante. Per esempio

Create clustered index t1f1 on t1(f1);
Update Statistics t1;

Il primo SELECT sopra farà una singola scansione dell'indice cluster. Il secondo SELECT eseguirà 4 ricerche di indice cluster, ma sono ancora più costose di una singola scansione di indice cluster. Ho provato la stessa cosa su un tavolo con 8 milioni di righe e il secondo SELECT era ancora molto più costoso.


23

Per MySQL, questo può essere abbreviato in:

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id

1
"gruppo per distributore_id" "era davvero necessario in questa query? Può funzionare anche senza quello
user1451111

2
@ user1451111 domanda originale capita quindi la risposta dipende dalla domanda stessa
Al-Mothafar

11

Bene, se devi avere tutto in una query, potresti fare un sindacato:

SELECT distributor_id, COUNT() FROM ... UNION
SELECT COUNT() AS EXEC_COUNT FROM ... WHERE level = 'exec' UNION
SELECT COUNT(*) AS PERSONAL_COUNT FROM ... WHERE level = 'personal';

Oppure, se è possibile farlo dopo l'elaborazione:

SELECT distributor_id, COUNT(*) FROM ... GROUP BY level;

Otterrai il conteggio per ogni livello e dovrai sommarli tutti per ottenere il totale.


Si è UNIONrivelato molto utile durante la generazione di un report contenente più istanze della COUNT(*)funzione.
James O

Il risultato mostra #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') FROM distributors UNION SELECT COUNT() AS EXEC_COUNT FROM distributors WHERE ' at line 1.
Istiaque Ahmed,

il numero di colonne restituite da tutte le query, su cui viene applicato UNION, deve essere uguale. @IstiaqueAhmed probabilmente è questo il motivo dell'errore.
user1451111

Una nota per chiunque inciamperà in questa risposta in futuro. La tecnica "Dopo l'elaborazione" descritta qui può causare problemi quando alcuni dei valori nelle colonne "livello" sono NULL. In tal caso, la somma di tutti i conteggi secondari non sarà uguale al conteggio totale delle righe.
user1451111

6

Faccio qualcosa del genere in cui do a ciascuna tabella un nome stringa per identificarlo nella colonna A e un conteggio per la colonna. Poi li unisco tutti in modo che si accumulino. Il risultato è piuttosto secondo me: non sono sicuro di quanto sia efficace rispetto ad altre opzioni, ma mi ha procurato ciò di cui avevo bisogno.

select 'table1', count (*) from table1
union select 'table2', count (*) from table2
union select 'table3', count (*) from table3
union select 'table4', count (*) from table4
union select 'table5', count (*) from table5
union select 'table6', count (*) from table6
union select 'table7', count (*) from table7;

Risultato:

-------------------
| String  | Count |
-------------------
| table1  | 123   |
| table2  | 234   |
| table3  | 345   |
| table4  | 456   |
| table5  | 567   |
-------------------

1
a query that I created makes ...- dov'è quella domanda?
Istiaque Ahmed,

2
come aggiungere dove caluse a tutte le tabelle

3

Basato sulla risposta accettata di Bluefeet con una sfumatura aggiunta usando OVER():

SELECT distributor_id,
    COUNT(*) total,
    SUM(case when level = 'exec' then 1 else 0 end) OVER() ExecCount,
    SUM(case when level = 'personal' then 1 else 0 end) OVER () PersonalCount
FROM yourtable
GROUP BY distributor_id

Usando OVER()con niente in () vi darà il conteggio totale per l'intero set di dati.


1

Penso che questo possa funzionare anche per te select count(*) as anc,(select count(*) from Patient where sex='F')as patientF,(select count(*) from Patient where sex='M') as patientM from anc

e puoi anche selezionare e contare le tabelle correlate come questa select count(*) as anc,(select count(*) from Patient where Patient.Id=anc.PatientId)as patientF,(select count(*) from Patient where sex='M') as patientM from anc

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.