¿Cómo obtener múltiples recuentos con una consulta SQL?

316

Me pregunto cómo escribir esta consulta.

Sé que esta sintaxis real es falsa, pero te ayudará a entender lo que quiero. Lo necesito en este formato, porque es parte de una consulta mucho más grande.

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

Necesito todo esto devuelto en una consulta.

Además, debe estar en una fila, por lo que lo siguiente no funcionará:

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'
Crobzilla
fuente
1
¿Esta consulta de usted funcionó correctamente? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Pratik

Respuestas:

690

Puede usar una CASEdeclaración con una función agregada. Esto es básicamente lo mismo que una PIVOTfunción en algunos 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
fuente
55
Fantástico, esto es asombroso. Gran respuesta. Solo una nota para las personas que han tropezado aquí. Recuento contará todas las filas, la suma hará lo mismo que un recuento cuando se usa con una declaración de caso.
John Ballinger
1
¡Solución brillante! Probablemente vale la pena señalar que este método funciona igual de bien si combina muchas tablas juntas en una consulta, ya que el uso de subconsultas puede ser bastante complicado en esa instancia.
Darren Crabb
77
Gracias por esta solución muy elegante. Por cierto, esto también funciona con TSQL.
Annie Lagang
66
Por qué esta podría no ser la mejor respuesta: siempre un escaneo completo de la tabla. Considere una combinación de subconsultas de recuento o recuentos anidados en una selección. Sin embargo, sin índices presentes, esto podría ser mejor ya que ha garantizado solo una exploración de tabla frente a múltiples. Ver respuesta de @KevinBalmforth
YoYo
1
@JohnBallinger, 'Count contará todas las filas' - COUNTcontará distributor_idsabiamente. No todas las filas de la tabla, ¿verdad?
Istiaque Ahmed
88

Una forma que funciona con seguridad

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 ;

EDITAR:
Vea el desglose del rendimiento de @ KevinBalmforth para saber por qué es probable que no desee utilizar este método y, en su lugar, deba optar por la respuesta de @ Taryn ♦. Dejo esto para que la gente pueda entender sus opciones.

Yo no
fuente
2
Esto me ayudó a resolver cómo hacer varios recuentos y generarlos en una sola instrucción SELECT con cada recuento como una columna. Funciona muy bien, ¡gracias!
Mark
2
Pude usar lo que proporcionaste aquí, en un proyecto mío. Ahora todo está en una sola consulta, en lugar de múltiples consultas. La página se carga en menos de un segundo, en comparación con 5-8 segundos con múltiples consultas. Quiéralo. Gracias Notme
Wayne Barron
1
Esto podría funcionar bien si cada subconsulta realmente alcanza un índice. Si no, entonces sum(case...)se debe considerar la solución.
YoYo
1
Tenga en cuenta que como alternativa a la distinción, como he hecho la corrección, también puede / mejor usar group bycon el beneficio de reemplazar una consulta anidada completa con una simple count(*)como muestra @Mihai, con más simplificaciones de sintaxis solo de MySQL.
YoYo
43
SELECT 
    distributor_id, 
    COUNT(*) AS TOTAL, 
    COUNT(IF(level='exec',1,null)),
    COUNT(IF(level='personal',1,null))
FROM sometable;

COUNTsolo cuenta non nullvalores y DECODEdevolverá un valor no nulo 1solo si se cumple su condición.

Majid Laissi
fuente
el cual distributor_idserá el espectáculo consulta? Muestra 1 fila en total.
Istiaque Ahmed
El OP tiene un grupo en la columna que se omitió en mi respuesta.
Majid Laissi
me salvaste la vida, todas las otras respuestas devuelven varias filas en MySQL. Muchas gracias
Abner
1
@Abner contento de que esto todavía ayude después de 8 años :)
Majid Laissi
@MajidLaissi sí, cambió el tiempo de consulta de un minuto a menos de un segundo. :)
Abner
25

Sobre la base de otras respuestas publicadas.

Ambos producirán los valores correctos:

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 ; 

Sin embargo, el rendimiento es bastante diferente, lo que obviamente será más relevante a medida que aumente la cantidad de datos.

Descubrí que, suponiendo que no se definieran índices en la tabla, la consulta que usa los SUMs haría un escaneo de una sola tabla, mientras que la consulta con los COUNT haría múltiples escaneos de la tabla.

Como ejemplo, ejecute el siguiente 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)

Resalte las 2 declaraciones SELECT y haga clic en el icono Mostrar plan de ejecución estimado. Verá que la primera instrucción hará un escaneo de tabla y la segunda hará 4. Obviamente, un escaneo de tabla es mejor que 4.

Agregar un índice agrupado también es interesante. P.ej

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

El primer SELECT anterior hará un solo escaneo de índice agrupado. El segundo SELECT realizará 4 búsquedas de índice agrupado, pero aún son más caras que un solo escaneo de índice agrupado. Intenté lo mismo en una mesa con 8 millones de filas y el segundo SELECT todavía era mucho más caro.

Kevin Balmforth
fuente
23

Para MySQL, esto se puede acortar a:

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id
Mihai
fuente
1
¿fue realmente necesario "group by distribuidor_id" "en esta consulta? También puede funcionar sin eso
user1451111
2
@ user1451111 la pregunta original lo obtuvo, por lo que su respuesta depende de la pregunta en sí misma
Al-Mothafar
11

Bueno, si debe tenerlo todo en una consulta, podría hacer una unión:

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

O, si puede hacerlo después del procesamiento:

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

Obtendrá el recuento de cada nivel y deberá sumarlos todos para obtener el total.

CrazyCasta
fuente
Se encontró UNIONque es muy útil al generar un informe que contiene varias instancias de la COUNT(*)función.
James O
El resultado muestra #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
El número de columnas devueltas de todas las consultas, en las que se aplica UNION, debe ser igual. @IstiaqueAhmed probablemente esa sea la razón detrás de su error.
user1451111
Una nota para cualquiera que tropiece con esta respuesta en el futuro. La técnica 'After Processing' descrita aquí puede causar problemas cuando algunos de los valores en las columnas 'level' son NULL. En ese caso, la suma de todos los subcuentas no será igual al recuento total de filas.
user1451111
6

Hago algo como esto donde solo le doy a cada tabla un nombre de cadena para identificarlo en la columna A, y un recuento para la columna. Luego los uní a todos para que se apilen. El resultado es bastante en mi opinión: no estoy seguro de cuán eficiente es en comparación con otras opciones, pero me dio lo que necesitaba.

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;

Resultado:

-------------------
| String  | Count |
-------------------
| table1  | 123   |
| table2  | 234   |
| table3  | 345   |
| table4  | 456   |
| table5  | 567   |
-------------------
Frantumn
fuente
1
a query that I created makes ...- ¿Dónde está esa consulta?
Istiaque Ahmed
2
cómo agregar where caluse a todas las tablas
3

Basado en la respuesta aceptada de Bluefeet con un matiz adicional 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

Usar OVER()sin nada en () le dará el recuento total de todo el conjunto de datos.

mentorrory
fuente
1

Creo que esto también puede funcionar para ti select count(*) as anc,(select count(*) from Patient where sex='F')as patientF,(select count(*) from Patient where sex='M') as patientM from anc

y también puede seleccionar y contar tablas relacionadas como esta 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
fuente