Как получить несколько подсчетов с помощью одного SQL-запроса?

Мне интересно, как написать этот запрос.

Я знаю, что этот фактический синтаксис является подделкой, но он поможет вам понять, чего я хочу. Мне он нужен в этом формате, потому что это часть гораздо большего запроса.

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

Мне нужно, чтобы все это было возвращено одним запросом.

Кроме того, он должен быть в одной строке, поэтому следующее не сработает:

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'

person Crobzilla    schedule 08.10.2012    source источник
comment
Правильно ли сработал этот ваш запрос? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'   -  person Pratik    schedule 13.03.2016


Ответы (9)


Вы можете использовать оператор CASE с агрегатной функцией. По сути, это то же самое, что и функция PIVOT в некоторых СУБД:

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
person Taryn    schedule 08.10.2012
comment
Потрясающе, это потрясающе. Отличный ответ. Просто на заметку людям, которые здесь наткнулись. Count будет считать все строки, сумма будет делать то же самое, что и count при использовании с оператором case. - person John Ballinger; 02.06.2014
comment
Гениальное решение! Вероятно, стоит отметить, что этот метод работает так же хорошо, если вы объединяете множество таблиц вместе в одном запросе, поскольку использование подзапросов в этом случае может стать довольно беспорядочным. - person Darren Crabb; 08.10.2015
comment
Спасибо за это очень элегантное решение. Кстати, это также работает с TSQL. - person Annie Lagang; 09.09.2016
comment
Почему это может быть не лучший ответ: всегда полное сканирование таблицы. Рассмотрим объединение подсчетов-подзапросов или вложенных подсчетов в выборке. Однако без индексов это может быть лучше всего, поскольку вы гарантировали только одно сканирование таблицы против нескольких. См. Ответ от @KevinBalmforth - person YoYo; 01.04.2017
comment
@JohnBallinger, 'Count будет считать все строки' - COUNT будет считать distributor_id мудро. не все строки таблицы, верно? - person Istiaque Ahmed; 08.11.2017
comment
@IstiaqueAhmed Да, это верно, он будет засчитан по distributor_id, поскольку это столбец группировки. - person Taryn; 08.11.2017
comment
действительно ли была необходима группировка по идентификатору дистрибьютора в этом запросе? Он может работать и без этого - person user1451111; 27.11.2017
comment
@ user1451111 Если вы не включите distributor_id, значит, вы не сообщаете движку, что именно вы хотите. Хотя запрос может работать и без него, MySQL может вернуть неверное значение для distributor_id, см. как MySQL обрабатывает Group By. Я бы предпочел быть конкретным и быть уверенным, что каждый раз он возвращает то, что я ожидал. - person Taryn; 27.11.2017
comment
Я пробовал запрос в MS SQL Server. Ваш упомянутый запрос (в ответе) отлично работает без GROUP BY, что касается ситуации OP и предполагаемого ответа. Добавление GROUP BY в конце создает две строки, разделенные по группам, и не показывает общее количество строк в таблице. Но опять же, я не могу проверить это для MySQL. - person user1451111; 10.06.2018
comment
@ user1451111, что вы имеете в виду, что он работает на SQL-сервере без Group By? SQL-серверу требуется group by, когда вы включаете столбец вне агрегата. - person Taryn; 11.06.2018
comment
Удивительно, это было ТОЧНО то, что мне нужно! Спасибо ! - person Frédéric Nobre; 14.03.2019
comment
Да спас мне жизнь этот запрос! БЛАГОДАРНОСТЬ! - person daticon; 13.05.2020
comment
В Postgres 9.4 или новее вы можете использовать предложение FILTER который дает тот же результат с более чистым синтаксисом: COUNT(*) FILTER (WHERE level = 'exec') AS ExecCount - person xthrd; 25.11.2020
comment
Фантастика! Наверное, я второй раз использую ваш ответ :-) - person funder7; 06.12.2020
comment
Ваше решение спасло мне день! Спасибо! - person Tạ Anh Tú; 05.01.2021
comment
действительно удивительно. Потратив некоторое время на своего разработчика, я погуглил и нашел это прекрасное решение. надо было сделать это прямо сейчас! - person Joel; 09.03.2021

Один способ, который наверняка работает

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 ;

РЕДАКТИРОВАТЬ:
См. разбивку производительности @ KevinBalmforth, чтобы узнать, почему вы, вероятно, не хотите использовать этот метод и вместо этого должны выбрать ответ @Taryn ♦. Я оставляю это, чтобы люди могли понять свои варианты.

person NotMe    schedule 08.10.2012
comment
Это помогло мне решить, как выполнять несколько подсчетов и выводить их в одном операторе SELECT, где каждый счетчик является столбцом. Отлично работает - спасибо! - person Mark; 23.01.2016
comment
Я смог использовать то, что вы здесь предоставили, в своем проекте. Теперь все находится в одном запросе, а не в нескольких запросах. Страница загружается менее чем за секунду по сравнению с 5-8 секундами при нескольких запросах. Любить это. Спасибо, Notme. - person Wayne Barron; 13.03.2017
comment
Это может сработать, если каждый подзапрос действительно попадает в индекс. Если нет, то следует рассмотреть sum(case...) решение. - person YoYo; 01.04.2017
comment
Обратите внимание, что в качестве альтернативы отличному, поскольку я сделал исправление, вы также можете / лучше использовать group by с преимуществом замены всего вложенного запроса на простой count(*), как показывает @Mihai, - с дальнейшими упрощениями синтаксиса MySQL. - person YoYo; 01.04.2017

SELECT 
    distributor_id, 
    COUNT(*) AS TOTAL, 
    COUNT(IF(level='exec',1,null)),
    COUNT(IF(level='personal',1,null))
FROM sometable;

COUNT подсчитывает только non null значения, а DECODE вернет ненулевое значение 1, только если ваше условие выполнено.

person Majid Laissi    schedule 08.10.2012
comment
какой distributor_id будет отображаться в запросе? Всего отображается 1 строка. - person Istiaque Ahmed; 08.11.2017
comment
У OP есть группа по столбцу, который был опущен в моем ответе. - person Majid Laissi; 08.11.2017
comment
вы спасли мне жизнь, все остальные ответы возвращают несколько строк в MySQL. Огромное спасибо - person Abner; 06.04.2020
comment
@ Абнер рад, что это все еще помогает через 8 лет :) - person Majid Laissi; 06.04.2020
comment
@MajidLaissi, да, изменил время запроса с минуты на менее секунды. :) - person Abner; 07.04.2020

Основываясь на других опубликованных ответах.

Оба они дадут правильные значения:

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 ; 

Однако производительность сильно отличается, что, очевидно, будет более актуальным по мере роста количества данных.

Я обнаружил, что при условии, что в таблице не определены индексы, запрос с использованием SUM будет выполнять сканирование одной таблицы, а запрос с COUNT - несколько сканирований таблиц.

В качестве примера запустите следующий скрипт:

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)

Выделите 2 оператора SELECT и щелкните значок «Показать предполагаемый план выполнения». Вы увидите, что первый оператор выполнит одно сканирование таблицы, а второй - 4. Очевидно, что одно сканирование таблицы лучше, чем 4.

Также интересно добавить кластерный индекс. Например.

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

Первый SELECT выше выполнит одно сканирование кластерного индекса. Второй SELECT выполнит 4 поиска по кластеризованному индексу, но они по-прежнему дороже, чем одно сканирование по кластеризованному индексу. Я попробовал то же самое с таблицей с 8 миллионами строк, и второй SELECT все еще был намного дороже.

person Kevin Balmforth    schedule 03.12.2015

Для MySQL это можно сократить до:

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id
person Mihai    schedule 03.12.2015
comment
действительно ли была необходима группировка по идентификатору дистрибьютора в этом запросе? Он может работать и без этого - person user1451111; 27.11.2017
comment
@ user1451111 исходный вопрос получил его, поэтому ответ зависит от самого вопроса - person Al-Mothafar; 09.10.2018

Что ж, если вам нужно все это в одном запросе, вы можете выполнить объединение:

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

Или, если вы можете сделать после обработки:

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

Вы получите счет для каждого уровня, и вам нужно будет просуммировать их все, чтобы получить общую сумму.

person CrazyCasta    schedule 08.10.2012
comment
Было обнаружено, что UNION очень полезен при создании отчета, содержащего несколько экземпляров функции COUNT(*). - person James O; 22.09.2015
comment
Результат показывает #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. - person Istiaque Ahmed; 08.11.2017
comment
количество столбцов, возвращаемых из всех запросов, к которым применяется UNION, должно быть одинаковым. @IstiaqueAhmed, вероятно, это причина вашей ошибки. - person user1451111; 10.06.2018
comment
Примечание для всех, кто в будущем наткнется на этот ответ. Описанный здесь метод «после обработки» может вызвать проблемы, когда некоторые значения в столбцах «уровень» равны ПУСТО (NULL). В этом случае сумма всех подсчетов не будет равна общему количеству строк. - person user1451111; 10.06.2018

Я делаю что-то подобное, где я просто даю каждой таблице строковое имя, чтобы идентифицировать ее в столбце A, и счетчик для столбца. Затем я объединяю их все, чтобы они складывались в стопку. На мой взгляд, результат хорош - не уверен, насколько он эффективен по сравнению с другими вариантами, но он дал мне то, что мне нужно.

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;

Результат:

-------------------
| String  | Count |
-------------------
| table1  | 123   |
| table2  | 234   |
| table3  | 345   |
| table4  | 456   |
| table5  | 567   |
-------------------
person Frantumn    schedule 20.09.2017
comment
a query that I created makes ...- где этот запрос? - person Istiaque Ahmed; 10.11.2017
comment
как добавить где калус во все таблицы - person ; 29.04.2018

На основе принятого ответа Bluefeet с добавленным нюансом с использованием 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

Использование OVER() без ничего в () даст вам общее количество для всего набора данных.

person mentorrory    schedule 09.11.2017

Думаю, это тоже может сработать select count(*) as anc,(select count(*) from Patient where sex='F')as patientF,(select count(*) from Patient where sex='M') as patientM from anc

а также вы можете выбирать и подсчитывать связанные таблицы, подобные этой 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

person Sinte    schedule 13.04.2018