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

316

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

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

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

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

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

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'
Crobzilla
источник
1
Правильно ли работал этот запрос? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Пратик

Ответы:

690

Вы можете использовать CASEоператор с агрегатной функцией. Это в основном то же самое, что и PIVOTфункция в некоторых 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
Тарын
источник
55
Фантастика, это потрясающе. Отличный ответ. Просто записка для людей, которые споткнулись здесь. Count будет подсчитывать все строки, сумма будет делать то же самое, что и count при использовании с оператором case.
Джон Баллинджер
1
Гениальное решение! Вероятно, стоит отметить, что этот метод работает так же хорошо, если вы объединяете множество таблиц в одном запросе, поскольку использование подзапросов в этом случае может привести к путанице.
Даррен Крабб
7
Спасибо за это очень элегантное решение. Кстати, это также работает с TSQL.
Энни Лаганг
6
Почему это может быть не лучшим ответом: всегда полное сканирование таблицы. Рассмотрим объединение подсчет-подзапросов или вложенных подсчетов в выборке. Однако при отсутствии индексов это может быть лучше, поскольку вы гарантировали только одно сканирование таблицы против нескольких. Смотрите ответ от @KevinBalmforth
YoYo
1
@JohnBallinger, «Count будет считать все строки» - COUNTбудет считать distributor_idмудрым. не все строки таблицы, верно?
Истак Ахмед
88

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

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 ♦. Я оставляю это, чтобы люди могли понять их варианты.

Не я
источник
2
Это помогло мне решить, как сделать несколько подсчетов и вывести их в одном операторе SELECT, где каждый счет является столбцом. Прекрасно работает - спасибо!
Марк
2
Я смог использовать то, что вы предоставили здесь, в моем проекте. Теперь все в одном запросе, а не в нескольких запросах. Страница загружается менее чем за секунду, по сравнению с 5-8 секундами с несколькими запросами. Любить это. Спасибо, Notme.
Уэйн Баррон
1
Это может хорошо работать, если каждый подзапрос фактически попадает в индекс. Если нет, то sum(case...)решение должно быть рассмотрено.
YoYo
1
Обратите внимание, что в качестве альтернативы другому, как я уже сделал исправление, вы также можете / лучше использовать его group byс выгодой замены целого вложенного запроса простым, count(*)как показывает @Mihai, - с дополнительными упрощениями синтаксиса в MySQL.
YoYo
43
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только если ваше условие выполнено.

Маджид Лаисси
источник
который distributor_idбудет показывать запрос? Всего показано 1 строка.
Истак Ахмед
У ОП есть столбец в столбце, который был опущен в моем ответе.
Маджид Лаисси
Вы спасли мою жизнь, все остальные ответы возвращают несколько строк в MySQL. Большое спасибо
Абнер
1
@Abner рад, что это помогает после 8 лет :)
Маджид Лайсси
@MajidLaissi: да, это изменило время моего запроса с минуты до менее секунды. :)
Абнер
25

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

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

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 все еще был намного дороже.

Кевин Бальмфорт
источник
23

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

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id
Михай
источник
1
действительно ли "group by distributor_id" "действительно необходим в этом запросе? Он может работать и без этого
user1451111
2
@ user1451111 оригинальный вопрос получил его, так что ответ зависит от самого вопроса
Аль-Мотафар
11

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

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;

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

CrazyCasta
источник
Оказалось UNIONочень полезным при создании отчета, содержащего несколько экземпляров COUNT(*)функции.
Джеймс О
Результат показывает #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.
Истак Ахмед
количество столбцов, возвращаемых из всех запросов, к которым применяется UNION, должно быть одинаковым. @IstiaqueAhmed, вероятно, в этом причина вашей ошибки.
user1451111
Примечание для тех, кто наткнется на этот ответ в будущем. Описанная здесь методика «After Processing» может вызвать проблему, когда некоторые значения в столбцах «level» имеют значение NULL. В этом случае сумма всех подсчетов не будет равна общему количеству строк.
user1451111
6

Я делаю что-то вроде этого, где я просто даю каждой таблице имя строки, чтобы идентифицировать ее в столбце 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   |
-------------------
Frantumn
источник
1
a query that I created makes ...- где этот запрос?
Истак Ахмед
2
как добавить, где caluse для всех таблиц
3

На основании принятого ответа 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()с нулем в () даст вам общее количество для всего набора данных.

mentorrory
источник
1

Я думаю, что это может также работать для вас 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

Sinte
источник