Wie erhalte ich mehrere Zählungen mit einer SQL-Abfrage?

315

Ich frage mich, wie ich diese Abfrage schreiben soll.

Ich weiß, dass diese tatsächliche Syntax falsch ist, aber sie wird Ihnen helfen zu verstehen, was ich will. Ich brauche es in diesem Format, weil es Teil einer viel größeren Abfrage ist.

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

Ich brauche dies alles in einer Abfrage zurückgegeben.

Außerdem muss es sich in einer Zeile befinden, damit Folgendes nicht funktioniert:

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'
Crobzilla
quelle
1
Hat diese Abfrage von Ihnen richtig funktioniert? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Pratik

Antworten:

689

Sie können eine CASEAnweisung mit einer Aggregatfunktion verwenden. Dies ist im Grunde dasselbe wie eine PIVOTFunktion in einigen 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
Taryn
quelle
55
Fantastisch, das ist unglaublich. Gute Antwort. Nur eine Notiz an Leute, die hier gestolpert sind. Count zählt alle Zeilen, die Summe entspricht der Zählung, wenn sie mit einer case-Anweisung verwendet wird.
John Ballinger
1
Geniale Lösung! Es ist wahrscheinlich erwähnenswert, dass diese Methode genauso gut funktioniert, wenn Sie viele Tabellen in einer Abfrage zusammenfassen, da die Verwendung von Unterabfragen in diesem Fall ziemlich chaotisch werden kann.
Darren Crabb
7
Vielen Dank für diese sehr elegante Lösung. Übrigens funktioniert dies auch mit TSQL.
Annie Lagang
6
Warum dies möglicherweise nicht die beste Antwort ist: Immer ein vollständiger Tabellenscan. Betrachten Sie eine Verknüpfung von Zählunterabfragen oder verschachtelten Zählungen in einer Auswahl. Wenn jedoch keine Indizes vorhanden sind, ist dies möglicherweise am besten, da Sie nur einen Tabellenscan gegenüber mehreren garantiert haben. Siehe Antwort von @KevinBalmforth
YoYo
1
@ JohnBallinger, 'Count zählt alle Zeilen' - COUNTzählt distributor_idweise. Nicht alle Zeilen der Tabelle, oder?
Istiaque Ahmed
87

Ein Weg, der sicher funktioniert

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:
Siehe @ KevinBalmforth die Aufschlüsselung der Leistung dafür , warum Sie wahrscheinlich nicht wollen , diese Methode verwenden und stattdessen für @ Taryn ♦ entscheiden sollten 's Antwort. Ich lasse das hier, damit die Leute ihre Optionen verstehen können.

Nicht ich
quelle
2
Dies hat mir geholfen, herauszufinden, wie mehrere Zählungen durchgeführt und in einer einzigen SELECT-Anweisung ausgegeben werden, wobei jede Zählung eine Spalte ist. Funktioniert super - danke!
Mark
2
Ich konnte das, was Sie hier zur Verfügung gestellt haben, in einem meiner Projekte verwenden. Jetzt befindet sich alles in einer einzigen Abfrage anstatt in mehreren Abfragen. Die Seite wird in weniger als einer Sekunde geladen, verglichen mit 5-8 Sekunden bei mehreren Abfragen. Liebe es. Danke, Notme.
Wayne Barron
1
Dies kann gut funktionieren, wenn jede Unterabfrage tatsächlich einen Index trifft. Wenn nicht, sum(case...)sollte eine Lösung in Betracht gezogen werden.
YoYo
1
Beachten Sie, dass Sie als Alternative zur Unterscheidung, wie ich die Korrektur vorgenommen habe, auch eine bessere group byverschachtelte Abfrage durch eine einfache Abfrage ersetzen können, count(*)wie @Mihai zeigt - mit weiteren Vereinfachungen der MySQL-Syntax.
YoYo
Dieser Weg funktioniert besser als JOINS, danke.
JohnA10
43
SELECT 
    distributor_id, 
    COUNT(*) AS TOTAL, 
    COUNT(IF(level='exec',1,null)),
    COUNT(IF(level='personal',1,null))
FROM sometable;

COUNTzählt nur non nullWerte und DECODEgibt 1nur dann einen Wert ungleich Null zurück, wenn Ihre Bedingung erfüllt ist.

Majid Laissi
quelle
Was distributor_idwird die Abfrage zeigen? Es zeigt insgesamt 1 Zeile.
Istiaque Ahmed
Das OP hat eine Gruppe von in der Spalte, die in meiner Antwort weggelassen wurde.
Majid Laissi
Sie haben mir das Leben gerettet, alle anderen Antworten geben mehrere Zeilen in MySQL zurück. Vielen Dank
Abner
1
@Abner froh, dass dies nach 8 Jahren immer noch hilft :)
Majid Laissi
@MajidLaissi Ja, meine Abfragezeit wurde von einer Minute auf weniger als eine Sekunde geändert. :)
Abner
25

Aufbauend auf anderen geposteten Antworten.

Beide ergeben die richtigen Werte:

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 ; 

Die Leistung ist jedoch sehr unterschiedlich, was offensichtlich mit zunehmender Datenmenge relevanter wird.

Ich stellte fest, dass unter der Annahme, dass keine Indizes für die Tabelle definiert wurden, die Abfrage mit den SUMs einen einzelnen Tabellenscan durchführen würde, während die Abfrage mit den COUNTs mehrere Tabellenscans durchführen würde.

Führen Sie als Beispiel das folgende Skript aus:

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)

Markieren Sie die 2 SELECT-Anweisungen und klicken Sie auf das Symbol "Geschätzten Ausführungsplan anzeigen". Sie werden sehen, dass die erste Anweisung einen Tabellenscan und die zweite 4 ausführt. Offensichtlich ist ein Tabellenscan besser als 4.

Interessant ist auch das Hinzufügen eines Clustered-Index. Z.B

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

Das erste SELECT oben führt einen einzelnen Clustered Index Scan durch. Das zweite SELECT führt 4 Clustered Index-Suchvorgänge durch, die jedoch immer noch teurer sind als ein einzelner Clustered Index-Scan. Ich habe dasselbe an einem Tisch mit 8 Millionen Zeilen versucht und das zweite SELECT war immer noch viel teurer.

Kevin Balmforth
quelle
23

Für MySQL kann dies verkürzt werden auf:

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id
Mihai
quelle
1
war "group by Distributor_id" "wirklich notwendig in dieser Abfrage? Es kann auch ohne das
funktionieren
2
@ user1451111 ursprüngliche Frage bekam es, so dass die Antwort von der Frage selbst abhängt
Al-Mothafar
11

Wenn Sie alles in einer Abfrage haben müssen, können Sie eine Union bilden:

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';

Oder wenn Sie nach der Verarbeitung tun können:

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

Sie erhalten die Anzahl für jedes Level und müssen sie alle zusammenfassen, um die Summe zu erhalten.

CrazyCasta
quelle
Es hat UNIONsich als sehr hilfreich erwiesen, wenn ein Bericht erstellt wird, der mehrere Instanzen der COUNT(*)Funktion enthält.
James O
Das Ergebnis zeigt #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
Die Anzahl der von allen Abfragen zurückgegebenen Spalten, auf die eine UNION angewendet wird, sollte gleich sein. @IstiaqueAhmed wahrscheinlich ist das der Grund für Ihren Fehler.
user1451111
Ein Hinweis für alle, die in Zukunft auf diese Antwort stoßen. Die hier beschriebene Technik "Nach der Verarbeitung" kann zu Problemen führen, wenn einige der Werte in den Spalten "Ebene" NULL sind. In diesem Fall entspricht die Summe aller Unterzählungen nicht der Gesamtzahl der Zeilen.
user1451111
6

Ich mache so etwas, indem ich jeder Tabelle einen String-Namen gebe, um sie in Spalte A zu identifizieren, und eine Anzahl für die Spalte. Dann vereinige ich sie alle, damit sie sich stapeln. Das Ergebnis ist meiner Meinung nach ziemlich gut - ich bin mir nicht sicher, wie effizient es im Vergleich zu anderen Optionen ist, aber es hat mir das gebracht, was ich brauchte.

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;

Ergebnis:

-------------------
| String  | Count |
-------------------
| table1  | 123   |
| table2  | 234   |
| table3  | 345   |
| table4  | 456   |
| table5  | 567   |
-------------------
Frantumn
quelle
1
a query that I created makes ...- Wo ist diese Abfrage?
Istiaque Ahmed
2
wie man wo caluse zu allen Tabellen hinzufügt
3

Basierend auf der von Bluefeet akzeptierten Antwort mit einer zusätzlichen Nuance unter Verwendung von 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

Wenn Sie OVER()nichts in () verwenden, erhalten Sie die Gesamtzahl für den gesamten Datensatz.

Mentorrory
quelle
1

Ich denke, das kann auch bei Ihnen funktionieren select count(*) as anc,(select count(*) from Patient where sex='F')as patientF,(select count(*) from Patient where sex='M') as patientM from anc

Außerdem können Sie verwandte Tabellen wie diese auswählen und zählen 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

Sinte
quelle