Como obter várias contagens com uma consulta SQL?

316

Eu estou querendo saber como escrever esta consulta.

Eu sei que essa sintaxe real é falsa, mas ajudará você a entender o que estou querendo. Eu preciso desse formato, porque faz parte de uma consulta muito maior.

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

Eu preciso disso tudo retornado em uma consulta.

Além disso, ele precisa estar em uma linha, para que o seguinte não funcione:

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'
Crobzilla
fonte
1
Esta sua consulta funcionou corretamente? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Pratik

Respostas:

690

Você pode usar uma CASEinstrução com uma função agregada. Isso é basicamente a mesma coisa que uma PIVOTfunção em alguns 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
fonte
55
Fantástico, isso é incrível. Ótima resposta. Apenas uma nota para as pessoas que tropeçaram aqui. Count contará todas as linhas, a soma fará a mesma coisa que uma contagem quando usada com uma instrução de caso.
precisa
1
Solução brilhante! Provavelmente vale a pena notar que esse método funciona tão bem se você estiver combinando várias tabelas em uma consulta, pois o uso de subconsultas pode ficar bastante confuso nesse caso.
Darren Crabb
7
Obrigado por esta solução muito elegante. Btw, isso também funciona com TSQL.
Annie Lagang
6
Por que essa pode não ser a melhor resposta: sempre uma verificação completa da tabela. Considere uma junção de subconsultas de contagem ou contagens aninhadas em uma seleção. No entanto, sem índices presentes, isso pode ser melhor, pois você garantiu apenas uma varredura de tabela versus várias. Veja resposta de @KevinBalmforth
YoYo
1
@JohnBallinger, 'Count contará todas as linhas' - COUNTcontará com distributor_idsabedoria. nem todas as linhas da tabela, certo?
Istiaque Ahmed
88

Uma maneira que funciona com certeza

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:
Consulte a quebra de desempenho de @ KevinBalmforth para saber por que você provavelmente não deseja usar esse método e, em vez disso, deve optar pela resposta de @ Taryn ♦. Estou deixando isso para que as pessoas possam entender suas opções.

Eu não
fonte
2
Isso me ajudou a resolver como fazer várias contagens e produzi-las em uma única instrução SELECT, sendo cada contagem uma coluna. Funciona muito bem - obrigado!
Mark
2
Consegui usar o que você forneceu aqui, em um projeto meu. Agora tudo está em uma única consulta, em vez de várias consultas. A página é carregada em menos de um segundo, em comparação com 5 a 8 segundos com várias consultas. Adoro. Obrigado, Notme.
Wayne Barron
1
Isso pode funcionar bem se cada subconsulta realmente atingir um índice. Caso contrário, a sum(case...)solução deve ser considerada.
YoYo
1
Observe que, como alternativa ao distinto, como fiz a correção, você também pode / melhor usar group bycom o benefício de substituir uma consulta aninhada inteira por uma simples, count(*)como mostra o @Mihai - com outras simplificações da sintaxe do MySQL.
YoYo 01/04
43
SELECT 
    distributor_id, 
    COUNT(*) AS TOTAL, 
    COUNT(IF(level='exec',1,null)),
    COUNT(IF(level='personal',1,null))
FROM sometable;

COUNTconta apenas non nullvalores e DECODEretornará um valor não nulo 1somente se sua condição for atendida.

Majid Laissi
fonte
qual distributor_idserá a consulta mostrada? Mostra 1 linha no total.
Istiaque Ahmed
O OP tem um grupo na coluna que foi omitido na minha resposta.
Majid Laissi
você salvou minha vida, todas as outras respostas retornam várias linhas no MySQL. Muito obrigado
Abner
1
@Abner feliz que isso ainda ajuda depois de 8 anos :)
Majid Laissi 06/04
@MajidLaissi sim, mudou meu tempo de consulta de um minuto para menos de um segundo. :)
Abner
25

Com base em outras respostas postadas.

Ambos produzirão os valores corretos:

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 ; 

No entanto, o desempenho é bem diferente, o que obviamente será mais relevante à medida que a quantidade de dados aumentar.

Descobri que, assumindo que nenhum índice foi definido na tabela, a consulta usando as SUMs faria uma única varredura de tabela, enquanto a consulta com os COUNTs faria várias varreduras de tabela.

Como exemplo, execute o seguinte 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)

Destaque as 2 instruções SELECT e clique no ícone Exibir plano de execução estimado. Você verá que a primeira instrução fará uma varredura de tabela e a segunda fará 4. Obviamente, uma varredura de tabela é melhor que 4.

Adicionar um índice em cluster também é interessante. Por exemplo

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

A primeira SELEÇÃO acima fará uma única Verificação de Índice em Cluster. O segundo SELECT fará 4 buscas de índice em cluster, mas elas ainda são mais caras que uma única verificação de índice em cluster. Tentei a mesma coisa em uma tabela com 8 milhões de linhas e o segundo SELECT ainda era muito mais caro.

Kevin Balmforth
fonte
23

Para o MySQL, isso pode ser reduzido para:

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id
Mihai
fonte
1
foi "agrupar por distributor_id" "realmente necessário nesta consulta? Ele pode funcionar sem isso também
user1451111
2
@ user1451111 pergunta original entendeu por isso é resposta depende da questão em si
Al-Mothafar
11

Bem, se você tiver tudo em uma consulta, poderá fazer uma união:

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, se você puder fazer após o processamento:

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

Você receberá a contagem de cada nível e precisará resumir todos eles para obter o total.

CrazyCasta
fonte
Considerou UNION-se muito útil ao gerar um relatório contendo várias instâncias da COUNT(*)função.
James O
O resultado 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
o número de colunas retornadas de todas as consultas nas quais um UNION é aplicado deve ser igual. @IstiaqueAhmed provavelmente essa é a razão por trás do seu erro.
user1451111
Uma observação para quem se deparar com essa resposta no futuro. A técnica 'After Processing' descrita aqui pode causar problemas quando alguns dos valores nas colunas 'level' são NULL. Nesse caso, a soma de todas as subcontagens não será igual à contagem total de linhas.
user1451111
6

Faço algo assim, onde apenas atribuo a cada tabela um nome de sequência para identificá-la na coluna A e uma contagem para a coluna. Então eu uno todos eles para que eles empilhem. O resultado é bonito na minha opinião - não tenho certeza de quão eficiente é comparado a outras opções, mas me deu o que eu precisava.

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
fonte
1
a query that I created makes ...- onde está essa consulta?
Istiaque Ahmed
2
como adicionar where caluse a todas as tabelas
3

Com base na resposta aceita pela Bluefeet com uma nuance 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()nada no () fornecerá a contagem total para todo o conjunto de dados.

mentorrory
fonte
1

Eu acho que isso também pode funcionar para você 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 também você pode selecionar e contar tabelas 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
fonte