Comment obtenir plusieurs comptes avec une seule requête SQL?

316

Je me demande comment écrire cette requête.

Je sais que cette syntaxe réelle est fausse, mais elle vous aidera à comprendre ce que je veux. J'en ai besoin dans ce format, car cela fait partie d'une requête beaucoup plus importante.

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

J'ai besoin que tout cela soit retourné dans une seule requête.

En outre, il doit être sur une seule ligne, de sorte que les éléments suivants ne fonctionneront pas:

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'
Crobzilla
la source
1
Cette requête vous a-t-elle fonctionné correctement ?? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Pratik

Réponses:

690

Vous pouvez utiliser une CASEinstruction avec une fonction d'agrégation. C'est fondamentalement la même chose qu'une PIVOTfonction dans certains SGBDR:

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
la source
55
Fantastique, c'est incroyable. Très bonne réponse. Juste une note aux gens qui ont trébuché ici. Count comptera toutes les lignes, la somme fera la même chose qu'un count lorsqu'elle est utilisée avec une instruction case.
John Ballinger
1
Solution brillante! Il est probablement intéressant de noter que cette méthode fonctionne aussi bien si vous combinez beaucoup de tables ensemble dans une seule requête, car l'utilisation de sous-requêtes peut devenir assez compliquée dans ce cas.
Darren Crabb du
7
Merci pour cette solution très élégante. Btw, cela fonctionne également avec TSQL.
Annie Lagang
6
Pourquoi ce n'est peut-être pas la meilleure réponse: toujours une analyse complète de la table. Considérez une jointure de sous-requêtes ou de nombres imbriqués dans une sélection. Cependant, en l'absence d'index, cela peut être préférable car vous avez garanti une seule analyse de table contre plusieurs. Voir la réponse de @KevinBalmforth
YoYo
1
@JohnBallinger, 'Count comptera toutes les lignes' - COUNTcomptera distributor_idsage. pas toutes les lignes du tableau, non?
Istiaque Ahmed
88

Une façon qui fonctionne à coup sûr

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:
Voir la ventilation des performances de @ KevinBalmforth pour savoir pourquoi vous ne voulez probablement pas utiliser cette méthode et devriez plutôt opter pour la réponse de @ Taryn ♦. Je laisse cela pour que les gens puissent comprendre leurs options.

Pas moi
la source
2
Cela m'a aidé à déterminer comment effectuer plusieurs comptages et les générer dans une seule instruction SELECT, chaque comptage étant une colonne. Fonctionne très bien - merci!
Mark
2
J'ai pu utiliser ce que vous avez fourni ici, dans un de mes projets. Maintenant, tout est dans une seule requête, au lieu de plusieurs requêtes. La page se charge en moins d'une seconde, contre 5 à 8 secondes avec plusieurs requêtes. Aimer. Merci, Notme.
Wayne Barron
1
Cela pourrait bien fonctionner si chaque sous-requête atteint réellement un index. Sinon, une sum(case...)solution doit être envisagée.
YoYo
1
Notez que comme alternative à distinct, comme j'ai fait la correction, vous pouvez également / mieux utiliser group byavec l'avantage de remplacer une requête imbriquée entière par une simple count(*)comme @Mihai le montre - avec d'autres simplifications de syntaxe MySQL uniquement.
YoYo
43
SELECT 
    distributor_id, 
    COUNT(*) AS TOTAL, 
    COUNT(IF(level='exec',1,null)),
    COUNT(IF(level='personal',1,null))
FROM sometable;

COUNTne compte que les non nullvaleurs et DECODEne renverra de valeur non nulle 1que si votre condition est satisfaite.

Majid Laissi
la source
que distributor_idmontrera la requête? Il affiche 1 ligne au total.
Istiaque Ahmed
Le PO a un groupe par sur la colonne qui a été omis dans ma réponse.
Majid Laissi
vous m'avez sauvé la vie, toutes les autres réponses renvoient plusieurs lignes dans MySQL. Merci beaucoup
Abner
1
@Abner heureux que cela aide toujours après 8 ans :)
Majid Laissi
@MajidLaissi oui, cela a changé le temps de requête d'une minute à moins d'une seconde. :)
Abner
25

S'appuyant sur d'autres réponses publiées.

Les deux produiront les bonnes valeurs:

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 ; 

Cependant, les performances sont assez différentes, ce qui sera évidemment plus pertinent à mesure que la quantité de données augmente.

J'ai trouvé que, en supposant qu'aucun index n'était défini sur la table, la requête utilisant les SUM ferait une seule analyse de table, tandis que la requête avec les COUNT ferait plusieurs analyses de table.

Par exemple, exécutez le script suivant:

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)

Mettez en surbrillance les 2 instructions SELECT et cliquez sur l'icône Afficher le plan d'exécution estimé. Vous verrez que la première instruction fera une analyse de table et la seconde fera 4. Évidemment, une analyse de table vaut mieux que 4.

L'ajout d'un index cluster est également intéressant. Par exemple

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

Le premier SELECT ci-dessus fera une seule analyse d'index en cluster. Le second SELECT effectuera 4 recherches d'index en cluster, mais elles sont toujours plus chères qu'une seule analyse d'index en cluster. J'ai essayé la même chose sur une table avec 8 millions de lignes et le deuxième SELECT était encore beaucoup plus cher.

Kevin Balmforth
la source
23

Pour MySQL, cela peut être raccourci comme suit:

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id
Mihai
la source
1
"group by distributeur_id" "était-il vraiment nécessaire dans cette requête? Cela peut aussi fonctionner sans cela
user1451111
2
@ user1451111 la question d'origine l'a donc c'est la réponse dépend de la question elle
Al-Mothafar
11

Eh bien, si vous devez tout avoir dans une seule requête, vous pouvez faire une union:

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

Ou, si vous pouvez le faire après le traitement:

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

Vous obtiendrez le décompte pour chaque niveau et devrez les résumer tous pour obtenir le total.

CrazyCasta
la source
Trouvé UNIONpour être très utile lors de la génération d'un rapport contenant plusieurs instances de la COUNT(*)fonction.
James O
Le résultat montre #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
le nombre de colonnes renvoyées par toutes les requêtes, auxquelles une UNION est appliquée, doit être égal. @IstiaqueAhmed c'est probablement la raison de votre erreur.
user1451111
Une note pour tous ceux qui tomberont sur cette réponse à l'avenir. La technique «After Processing» décrite ici peut provoquer un problème lorsque certaines des valeurs des colonnes «level» sont NULL. Dans ce cas, la somme de tous les sous-comptages ne sera pas égale au nombre total de lignes.
user1451111
6

Je fais quelque chose comme ça où je donne juste à chaque table un nom de chaîne pour l'identifier dans la colonne A, et un compte pour la colonne. Ensuite, je les ai tous réunis pour qu'ils s'empilent. Le résultat est joli à mon avis - je ne sais pas à quel point il est efficace par rapport aux autres options, mais il m'a donné ce dont j'avais besoin.

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;

Résultat:

-------------------
| String  | Count |
-------------------
| table1  | 123   |
| table2  | 234   |
| table3  | 345   |
| table4  | 456   |
| table5  | 567   |
-------------------
Frantumn
la source
1
a query that I created makes ...- où est cette requête?
Istiaque Ahmed
2
comment ajouter où caluse à toutes les tables
3

Basé sur la réponse acceptée de Bluefeet avec une nuance supplémentaire en utilisant 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

L'utilisation OVER()de rien dans le () vous donnera le nombre total pour l'ensemble de données entier.

mentorale
la source
1

Je pense que cela peut aussi fonctionner pour vous select count(*) as anc,(select count(*) from Patient where sex='F')as patientF,(select count(*) from Patient where sex='M') as patientM from anc

et vous pouvez également sélectionner et compter les tables connexes comme celle-ci 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
la source