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

171

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

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

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

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

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

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'
  • 1
    Правильно ли работал этот запрос? SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Теги:
join
count
group-by

8 ответов

367
Лучший ответ

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

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
  • 40
    Фантастика, это потрясающе. Отличный ответ. Просто записка для людей, которые споткнулись здесь. Count будет подсчитывать все строки, сумма будет действовать так же, как и count при использовании с оператором case.
  • 1
    Гениальное решение! Вероятно, стоит отметить, что этот метод работает так же хорошо, если вы объединяете множество таблиц в одном запросе, поскольку использование подзапросов в этом случае может привести к путанице.
Показать ещё 9 комментариев
54

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

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:
 См. @KevinBalmforth о снижении производительности за то, почему вы, вероятно, не хотите использовать этот метод, и вместо этого должны выбрать ответ @bluefeet. Я оставляю это, чтобы люди могли понять их варианты.

  • 1
    Это помогло мне решить, как сделать несколько подсчетов и вывести их в одном операторе SELECT, где каждый счет является столбцом. Прекрасно работает - спасибо!
  • 1
    Можно ли это сделать в Hibernate Criteria?
Показать ещё 3 комментария
28
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, только если ваше условие выполнено.

  • 3
    Я уверен, что декодирование специфично для оракула
  • 0
    @ChrisLively это правильно.
Показать ещё 2 комментария
15

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

select distributor_id,
    count(*) total,
    sum(level = 'exec') ExecCount,
    sum(level = 'personal') PersonalCount
from yourtable
group by distributor_id
  • 0
    действительно ли "group by distributor_id" "действительно необходим в этом запросе? Он может работать и без этого
  • 0
    @ user1451111 оригинальный вопрос получил, так что ответ зависит от самого вопроса
11

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

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

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 будет выполнять несколько сканирований таблиц.

В качестве примера запустите следующий 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)

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

Добавление кластерного индекса также интересно. Например.

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

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

7

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

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;

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

  • 0
    Оказалось, что UNION очень полезен при создании отчета, содержащего несколько экземпляров функции COUNT(*) .
  • 0
    Результат показывает #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 .
Показать ещё 2 комментария
1

Я делаю что-то вроде этого, когда я просто даю каждой таблице имя строки, чтобы идентифицировать ее в столбце 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   |
-------------------
  • 0
    Как ты мог получить так много столов?
  • 0
    a query that I created makes ... - где этот запрос?
Показать ещё 1 комментарий
0

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню