Общая сводка с несколькими 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

Я знаком с ROLLUPs, но не могу найти комбинацию, которая даст мне то, что я ищу. Использование GROUP BY ROLLUP(country, province) включает в себя общее значение, которое я хочу, но также включает большое количество дополнительных значений, которые мне не нужны. Это также относится и к GROUP BY ROLLUP(country), province

Как я могу сделать "общую" запись?
В настоящее время я вычисляю ее с помощью UNION ALL и повторяю 90% первого запроса с другим GROUP BY, но поскольку первый запрос нетривиален, результат медленный и уродливый код.

Вот скрипт SQL для тех, кто хочет поиграть с этим: http://sqlfiddle.com/#!4/12ad9/5


person Mr. Llama    schedule 09.09.2014    source источник
comment
возможный дубликат Добавить итоговую строку с итогами   -  person Bulat    schedule 10.09.2014
comment
@Bulat - это для одного столбца в GROUP BY и использует специальный синтаксис SQL Server.   -  person Mr. Llama    schedule 10.09.2014
comment
Что ж, вы можете найти там тот же ответ и проголосовать за него. Если бы только количество столбцов и их названия были важны ... И все же это +1 за хорошо представленный вопрос.   -  person Bulat    schedule 10.09.2014


Ответы (6)


Это именно то, что GROUPING SETS< /strong> выражения были разработаны для:

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.

person ypercubeᵀᴹ    schedule 09.09.2014
comment
Прохладный! Теперь, как мне определить сводную строку с помощью функции GROUPING? Кажется, я не могу передать что-либо без ошибки. Я также узнал, что ROLLUP( (country, province) ) тоже работает, но вы опередили меня с ответом. :D - person Mr. Llama; 10.09.2014
comment
Не обращайте внимания на мой предыдущий комментарий. GROUPING возвращает 1, когда столбец пуст из-за ROLLUP, поэтому GROUPING(country) и GROUPING(province) работают. - person Mr. Llama; 10.09.2014
comment
Кстати (относительно вашего удаленного ответа), GROUP BY 1 обычно означает что-то другое. GROUP BY () на самом деле является допустимым SQL для общей группы по, что эквивалентно отсутствию предложения группы по. - person ypercubeᵀᴹ; 10.09.2014
comment
@Mr.Llama: Пожалуйста, рассмотрите возможность публикации вашего ROLLUP( (country, province) ) решения в качестве ответа. Это имеет смысл в вашем сценарии, и лично я нахожу его более ясным, чем этот ответ, как бы мне ни нравилось голосовать за сообщения ypercube. - person Andriy M; 10.09.2014
comment
@ Mr.Llama, AndriyM прав, GROUP BY ROLLUP( (country, province) ) должен быть ответом - и уж точно не в вопросе. Пожалуйста, добавьте его в свой ответ (и восстановите). - person ypercubeᵀᴹ; 10.09.2014
comment
@ypercube - восстановил мой ответ и добавил объяснение для подходов GROUP BY и ROLLUP. - person Mr. Llama; 10.09.2014
comment
Здравствуйте, сэр, можно ли получить сводку по каждой группе, то есть сводку по группе? - person Sagar Chaudhary; 28.09.2018
comment
@SagarChaudhary да, просто добавьте еще один набор для группировки. Например: GROUP BY GROUPING SETS ( (country, province), (country), () );, чтобы также добавить сводки по странам. - person ypercubeᵀᴹ; 28.09.2018

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


Первое решение включает 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

person Mr. Llama    schedule 09.09.2014

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

person Gordon Linoff    schedule 09.09.2014

Первое, что приходит на ум, это отфильтровать промежуточные итоги после применения 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
person Allan    schedule 09.09.2014

Вы можете использовать Union:

SELECT country, province, SUM(population)
FROM census
GROUP BY country, province
UNION
SELECT
   'Total', '', SUM(population)
FROM census
person DLeh    schedule 09.09.2014
comment
На оригинальном плакате говорилось, что они уже делают UNION, и это было неэффективно и уродливо. Скрипт SQL подробно показал это. Они хотели избежать союза. - person Anssssss; 10.09.2014

Я придумал 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;
person Mic1780    schedule 09.09.2014