Як отримати кілька підрахунків за допомогою одного SQL-запиту?


315

Мені цікаво, як написати цей запит.

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

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'
Пратік

Відповіді:


689

Можна використовувати 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
Фантастично, це дивовижно. Чудова відповідь. Просто записка до людей, які натрапили сюди. Підрахунок буде рахувати всі рядки, сума зробить те саме, що і підрахунок при використанні з випискою справи.
Джон Баллінгер

1
Блискуче рішення! Напевно, варто відзначити, що цей метод працює настільки ж чудово, якщо ви поєднуєте безліч таблиць разом в одному запиті, оскільки використання підзапитів може стати досить безладним у цьому випадку.
Даррен Крабб

7
Дякую за це дуже елегантне рішення. До речі, це також працює з TSQL.
Енні Лаганг

6
Чому це може бути не найкращою відповіддю: завжди повне сканування таблиці. Розглянемо об'єднання підзапросів підрахунку чи вкладених підрахунків у виді. Однак, якщо немає індексів, це може бути найкращим, оскільки ви гарантували лише одне сканування таблиці проти кількох. Дивіться відповідь від @KevinBalmforth
YoYo

1
@JohnBallinger, "Порахувати буде рахувати всі рядки" - COUNTвважатиметься distributor_idмудрим. не всі ряди столу, правда?
Істіак Ахмед

87

Один із способів, який працює точно

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
справді необхідним у цьому запиті було "групувати по дистриб'ютору_id"? Це може працювати і без цього
user1451111

2
@ user1451111 Оригінальне запитання отримало, тож відповідь залежить від самого питання
Al-Mothafar

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;

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


Він 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, ймовірно, це причина вашої помилки.
користувач1451111

Записка для кожного, хто натрапить на цю відповідь у майбутньому. Описана тут техніка "Після обробки" може спричинити проблему, коли деякі значення у стовпцях "рівень" мають значення NULL. У цьому випадку сума всіх підрахунків не буде дорівнює загальній кількості рядків.
user1451111

6

Я роблю щось подібне, коли я просто даю назвою рядку кожну таблицю, щоб ідентифікувати її у стовпці А, і рахувати колонку. Потім я об'єдную їх усіх, щоб вони склалися. Результат, на мій погляд, досить - не впевнений, наскільки він ефективний порівняно з іншими варіантами, але він отримав мені те, що мені потрібно.

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

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()нічого не використовувати в (), ви отримаєте загальний підрахунок за весь набір даних.


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.