Jak uzyskać wiele zliczeń za pomocą jednego zapytania SQL?

315

Zastanawiam się, jak napisać to zapytanie.

Wiem, że ta faktyczna składnia jest nieprawdziwa, ale pomoże ci zrozumieć, czego chcę. Potrzebuję go w tym formacie, ponieważ jest on częścią znacznie większego zapytania.

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

Potrzebuję tego wszystkiego w jednym zapytaniu.

Ponadto musi znajdować się w jednym wierszu, więc następujące elementy nie będą działać:

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'
Crobzilla
źródło
1
Czy to zapytanie działało poprawnie? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Pratik

Odpowiedzi:

689

Możesz użyć CASEinstrukcji z funkcją agregującą. Jest to w zasadzie to samo, co PIVOTfunkcja w niektórych 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
źródło
55
Fantastycznie, to jest niesamowite. Świetna odpowiedź. Uwaga dla osób, które się tu potknęły. Licznik zlicza wszystkie wiersze, suma robi to samo, co liczenie, gdy jest używana z instrukcją case.
John Ballinger
1
Genialne rozwiązanie! Prawdopodobnie warto zauważyć, że ta metoda działa tak samo dobrze, jeśli łączysz wiele tabel razem w jednym zapytaniu, ponieważ użycie pod-zapytań może być w tym przypadku dość nieuporządkowane.
Darren Crabb
7
Dzięki za to bardzo eleganckie rozwiązanie. Btw, to działa również z TSQL.
Annie Lagang,
6
Dlaczego nie jest to najlepsza odpowiedź: zawsze pełny skan tabeli. Rozważ dołączenie podkwerend lub zliczanie zagnieżdżone w zaznaczeniu. Jednak przy braku indeksów może to być najlepsze, ponieważ masz zagwarantowane tylko jedno skanowanie tabeli zamiast wielu. Zobacz odpowiedź od @KevinBalmforth
YoYo
1
@JohnBallinger, „Count będzie liczyć wszystkie wiersze” - COUNTbędzie liczyć distributor_idmądrze. nie wszystkie rzędy tabeli, prawda?
Istiaque Ahmed,
87

Jeden sposób, który na pewno działa

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 ;

EDYCJA:
Zobacz rozkład wydajności @ KevinBalmforth, aby dowiedzieć się, dlaczego prawdopodobnie nie chcesz używać tej metody, a zamiast tego powinieneś wybrać odpowiedź @ Taryn ♦. Zostawiam to, aby ludzie mogli zrozumieć ich opcje.

Nie ja
źródło
2
Pomogło mi to ustalić, jak wykonać wiele zliczeń i wyprowadzić je w pojedynczej instrukcji SELECT, przy czym każda liczba jest kolumną. Działa świetnie - dzięki!
Mark
2
Byłem w stanie wykorzystać to, co tu podałeś, w moim projekcie. Teraz wszystko jest w jednym zapytaniu, zamiast wielu zapytań. Strona ładuje się w czasie krótszym niż sekunda, w porównaniu z 5-8 sekundami przy wielu zapytaniach. Kocham to. Dzięki, Notme.
Wayne Barron
1
Może to działać dobrze, jeśli każde zapytanie podrzędne rzeczywiście trafi do indeksu. Jeśli nie, to sum(case...)należy rozważyć rozwiązanie.
YoYo
1
Zauważ, że jako alternatywa dla odrębnych, ponieważ wprowadziłem korektę, możesz również / lepiej użyć group byz korzyścią zastąpienia całego zagnieżdżonego zapytania prostym count(*)jak pokazuje @Mihai - z dalszymi uproszczeniami składni tylko w MySQL.
YoYo
43
SELECT 
    distributor_id, 
    COUNT(*) AS TOTAL, 
    COUNT(IF(level='exec',1,null)),
    COUNT(IF(level='personal',1,null))
FROM sometable;

COUNTliczy tylko non nullwartości, a DECODEzwróci wartość inną niż null 1tylko wtedy, gdy warunek jest spełniony.

Majid Laissi
źródło
co distributor_idpokaże zapytanie? Pokazuje w sumie 1 wiersz.
Istiaque Ahmed,
OP ma grupę według kolumny, która została pominięta w mojej odpowiedzi.
Majid Laissi
uratowałeś mi życie, wszystkie inne odpowiedzi zwracają wiele wierszy w MySQL. Wielkie dzięki
Abner,
1
@Abner cieszy się, że nadal pomaga to po 8 latach :)
Majid Laissi
@MajidLaissi tak, tak, zmieniłem czas zapytania z minuty na mniej niż sekundę. :)
Abner
25

Opierając się na innych opublikowanych odpowiedziach.

Oba z nich wygenerują prawidłowe wartości:

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 ; 

Jednak wydajność jest zupełnie inna, co oczywiście będzie bardziej odpowiednie w miarę wzrostu ilości danych.

Odkryłem, że przy założeniu braku indeksów w tabeli, zapytanie wykorzystujące SUMy wykonałoby skanowanie pojedynczej tabeli, podczas gdy zapytanie z LICZNIKAMI wykonałoby wiele skanów tabeli.

Na przykład uruchom następujący skrypt:

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)

Podświetl 2 instrukcje SELECT i kliknij ikonę Wyświetl szacowany plan wykonania. Zobaczysz, że pierwsza instrukcja wykona jedno skanowanie tabeli, a druga zrobi 4. Oczywiście jedno skanowanie tabeli jest lepsze niż 4.

Interesujące jest także dodanie indeksu klastrowego. Na przykład

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

Pierwszy WYBÓR powyżej wykona pojedyncze skanowanie indeksu klastrowanego. Drugi WYBÓR wykona 4 Wyszukiwanie indeksów klastrowych, ale są one nadal droższe niż pojedyncze skanowanie indeksów klastrowych. Próbowałem tego samego na stole z 8 milionami wierszy, a drugi SELECT był wciąż znacznie droższy.

Kevin Balmforth
źródło
23

W przypadku MySQL można to skrócić do:

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id
Mihai
źródło
1
było naprawdę „group by distributor_id” ”naprawdę potrzebne w tym zapytaniu? Może również działać bez tego
user1451111
2
@ user1451111 oryginalne pytanie dostało je, więc odpowiedź zależy od samego pytania
Al-Mothafar,
11

Cóż, jeśli musisz mieć to wszystko w jednym zapytaniu, możesz utworzyć połączenie:

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

Lub, jeśli możesz to zrobić po przetworzeniu:

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

Otrzymasz obliczenia dla każdego poziomu i musisz zsumować je wszystkie, aby uzyskać sumę.

CrazyCasta
źródło
Stwierdzono, UNIONże jest bardzo pomocny podczas generowania raportu zawierającego wiele instancji COUNT(*)funkcji.
James O
Wynik pokazuje #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,
liczba kolumn zwróconych ze wszystkich zapytań, do których zastosowano UNION, powinna być równa. @IstiaqueAhmed prawdopodobnie jest to przyczyną Twojego błędu.
user1451111
Uwaga dla każdego, kto natknie się na tę odpowiedź w przyszłości. Opisana tutaj technika „After Processing” może powodować problemy, gdy niektóre wartości w kolumnach „level” mają wartość NULL. W takim przypadku suma wszystkich podliczeń nie będzie równa całkowitej liczbie wierszy.
user1451111
6

Robię coś takiego, w którym po prostu nadaję każdej tabeli nazwę ciągu, aby zidentyfikować ją w kolumnie A, i liczbę kolumn. Następnie łączę je wszystkie, aby się ułożyły. Moim zdaniem wynik jest dość - nie jestem pewien, jak skuteczny jest w porównaniu z innymi opcjami, ale dostałem to, czego potrzebowałem.

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;

Wynik:

-------------------
| String  | Count |
-------------------
| table1  | 123   |
| table2  | 234   |
| table3  | 345   |
| table4  | 456   |
| table5  | 567   |
-------------------
Frantumn
źródło
1
a query that I created makes ...- gdzie jest to zapytanie?
Istiaque Ahmed,
2
jak dodać caluse do wszystkich tabel
3

Na podstawie zaakceptowanej odpowiedzi Bluefeet z dodatkowym niuansem przy użyciu 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

Używanie OVER()z niczym w () da ci całkowitą liczbę dla całego zestawu danych.

mentorrory
źródło
1

Myślę, że to też może dla ciebie zadziałać select count(*) as anc,(select count(*) from Patient where sex='F')as patientF,(select count(*) from Patient where sex='M') as patientM from anc

a także możesz wybierać i liczyć podobne tabele 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
źródło