Общее резюме с несколькими группами GROUP BY
Скажем, у меня есть таблица под названием census
со следующей информацией:
COUNTRY PROVINCE CITY POPULATION
==============================================
USA California Sacramento 1234
USA California SanFran 4321
USA Texas Houston 1111
USA Texas Dallas 2222
Canada Ontario Ottawa 3333
Canada Manitoba Winnipeg 4444
Я строю отчет на уровне страны/провинции, который дает мне следующее:
SELECT country, province, SUM(population)
FROM census
GROUP BY country, province;
COUNTRY PROVINCE SUM(POPULATION)
=======================================
USA California 5555
USA Texas 3333
Canada Ontario 3333
Canada Manitoba 4444
Я хочу, чтобы в отчете содержалась "общая сумма", так что конечный результат выглядит следующим образом:
COUNTRY PROVINCE SUM(POPULATION)
=======================================
USA California 5555
USA Texas 3333
Canada Ontario 3333
Canada Manitoba 4444
TOTAL 16665
Я знаком с ROLLUP
s, но я не могу найти комбинацию, которая получает меня, что я ищу. Использование GROUP BY ROLLUP(country, province)
включает в себя общее значение, которое я хочу, но оно также включает большое количество дополнительных значений, которые мне не нужны. Это также верно при GROUP BY ROLLUP(country), province
Как я могу сделать "полную" запись?
В настоящее время я вычисляю его с помощью UNION ALL
и повторяю 90% первого запроса с помощью другого GROUP BY
, но поскольку первый запрос нетривиален, результатом является медленный и уродливый код.
Здесь SQL Fiddle для тех, кто хочет играть с этим: http://sqlfiddle.com/#!4/12ad9/5
Ответы
Ответ 1
Хорошо, я наконец подошел к двум подходам, которые являются гибкими и не заставляют меня чувствовать себя ужасным программистом.
Первое решение включает GROUPING SETS
.
То, что я в основном пытаюсь сделать, это группировать выражение на двух разных уровнях: одно на общем уровне и одно на уровне (country, province)
.
Если бы я разделил запрос на две части и использовал UNION ALL
, одна половина имела бы GROUP BY country, province
, а вторая не имела бы предложения группировки. Негруппированный раздел также может быть представлен как GROUP BY ()
, если нам это нравится. Это пригодится через мгновение.
Это дает нам что-то вроде:
SELECT country, province, SUM(population)
FROM census
GROUP BY country, province
UNION ALL
SELECT NULL AS country, NULL AS province, SUM(population)
FROM census
GROUP BY ();
Запрос работает, но он плохо масштабируется. Чем больше вычислений вам нужно сделать, тем больше времени вы проводите, повторяя себя.
Используя GROUPING SETS
, я могу указать, что я хочу, чтобы данные были сгруппированы двумя разными способами:
SELECT country, province, SUM(population)
FROM census
GROUP BY GROUPING SETS( (country, province), () );
Теперь мы куда-то попадаем! Но как насчет строки результатов? Как мы можем обнаружить его и обозначить соответствующим образом? Что происходит, когда функция GROUPING
. Она возвращает 1, если столбец NULL из-за оператора GROUP BY.
SELECT
CASE
WHEN GROUPING(country) = 1 THEN 'TOTAL'
ELSE country
END AS country,
province,
SUM(population),
GROUPING(country) AS grouping_flg
FROM census
GROUP BY GROUPING SETS ( (country, province), () );
Если нам не нравится подход GROUPING SETS
, мы все равно можем использовать традиционный ROLLUP
, но с незначительными изменениями.
Вместо того, чтобы передавать каждый столбец в ROLLUP
индивидуально, мы передаем коллекцию столбцов как набор, заключая их в круглые скобки. Это делает так, что набор столбцов рассматривается как одна группа вместо нескольких групп. Следующий запрос даст вам те же результаты, что и предыдущий:
SELECT
CASE
WHEN GROUPING(country) = 1 THEN 'TOTAL'
ELSE country
END AS country,
province,
SUM(population),
GROUPING(country) AS grouping_flg
FROM census
GROUP BY ROLLUP( (country, province) );
Не стесняйтесь попробовать оба подхода для себя!
http://sqlfiddle.com/#!4/12ad9/102
Ответ 2
Это именно то, что предназначено для GROUPING SETS
:
SELECT country, province, SUM(population)
FROM census
GROUP BY GROUPING SETS
( (country, province), -- first group by country and province
() -- then by (nothing), i.e. a total grouping
);
См. SQL-Fiddle
Ответ 3
В Oracle вы можете сделать это с помощью предложения having
:
SELECT coalesce(c.country, 'Total') as province, c.country, SUM(c.population)
FROM census c
GROUP BY ROLLUP(c.country, c.province)
HAVING c.province is not null or
c.province is null and c.country is null;
Здесь - скрипт SQL.
Ответ 4
Первое, что приходит на ум, - это отфильтровать подтаблицы после применения rollup
:
SELECT *
FROM (SELECT country, province, SUM (population)
FROM census
GROUP BY ROLLUP (country, province))
WHERE province IS NOT NULL OR country IS NULL;
Вы можете сделать то же самое немного более компактно, используя GROUPING_ID
в предложении HAVING
:
SELECT country,
province,
SUM (population)
FROM census
GROUP BY ROLLUP (country, province)
HAVING GROUPING_ID (country, province) <> 1
И, как отметил @Anssssss, вы также можете использовать критерии из предложения WHERE
в первом ответе в предложении HAVING
:
SELECT country, province, SUM (population)
FROM census
GROUP BY ROLLUP (country, province)
HAVING province IS NOT NULL OR country IS NULL
Ответ 5
Вы можете использовать Union:
SELECT country, province, SUM(population)
FROM census
GROUP BY country, province
UNION
SELECT
'Total', '', SUM(population)
FROM census
Ответ 6
Ive придумал sql, используя Union, чтобы добавить Total к концу ваших результатов. Вы можете увидеть здесь
SELECT country, province, SUM(population) as population, 0 as OrderBy
FROM census
GROUP BY country, province
UNION
SELECT country, province, population, 1 as OrderBy FROM (
SELECT 'Total' as country, '' as province, SUM(population) as population
FROM census
)
ORDER BY OrderBy;