Общее резюме с несколькими группами 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;