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


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

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

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

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

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

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

9 ответов:

можно использовать 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

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

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

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 только если ваше условие выполнено.

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

select distributor_id,
    count(*) total,
    sum(level = 'exec') ExecCount,
    sum(level = 'personal') PersonalCount
from yourtable
group by distributor_id

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

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

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 ; 

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

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

в качестве примера, выполните следующие сценарий:

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 будет делать 4 кластеризованных индексных поиска, но они все еще дороже чем один кластеризованный индекс сканирования. Я попробовал то же самое на таблице с 8 миллионами строк, и второй выбор был все еще намного дороже.

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

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;

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

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

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

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