总结多个GROUP BY

时间:2014-09-09 20:53:03

标签: sql oracle group-by rollup grouping-sets

假设我有一个名为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,但我似乎无法找到一个能让我了解我想要的东西的组合。使用GROUP BY ROLLUP(country, province)包含我想要的总价值,但它还包含大量额外值,我并不关心。 GROUP BY ROLLUP(country), province

也是如此

我怎样才能完成"总计"记录?
我目前正在使用UNION ALL进行计算,并使用不同的GROUP BY重复第一个查询的90%,但由于第一个查询非常重要,因此结果是缓慢且难看的代码。

对于那些想要玩这个游戏的人来说,这是一个SQL小提琴:http://sqlfiddle.com/#!4/12ad9/5

6 个答案:

答案 0 :(得分:13)

这正是 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

答案 1 :(得分:6)

好的,我终于提出了两种灵活的方法,不会让我觉得自己像个可怕的程序员。


第一个解决方案涉及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函数的来源。如果由于GROUP BY语句而导致列为NULL,则返回1。

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 :(得分: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;

Here是SQL小提琴。

答案 3 :(得分:2)

首先想到的是在应用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

答案 4 :(得分:-1)

你可以使用联盟:

SELECT country, province, SUM(population)
FROM census
GROUP BY country, province
UNION
SELECT
   'Total', '', SUM(population)
FROM census

答案 5 :(得分:-1)

我想出了一个使用Union的SQL来将Total添加到结果的末尾。您可以看到query here

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;