【问题标题】:Overall summary with multiple GROUP BY具有多个 GROUP BY 的总体摘要
【发布时间】:2014-11-03 09:23:35
【问题描述】:

假设我有一个名为 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 计算它,并使用不同的 GROUP BY 重复第一个查询的 90%,但由于第一个查询很重要,因此结果是缓慢而丑陋的代码。

这里有一个 SQL Fiddle 供想要玩这个的人使用:http://sqlfiddle.com/#!4/12ad9/5

【问题讨论】:

  • @Bulat - 用于GROUP BY 中的单个列,并使用 SQL Server 特定的语法。
  • 好吧,你可以在那里找到相同的答案并给它投票。如果只有列的数量和它们的名称很重要……而且对于提出的问题,它仍然是 +1。

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


【解决方案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 函数的用武之地。如果由于 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,而是通过将列的集合括在括号中来将它们作为一个集合传递。这使得列集被视为 single 组而不是 multiple 组。以下查询将为您提供与上一个相同的结果:

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

    【讨论】:

    • 酷!现在如何使用GROUPING 函数检测摘要行?如果不引发错误,我似乎无法传递任何东西。我还发现ROLLUP( (country, province) ) 也有效,但你比我更胜一筹。 :D
    • 忽略我之前的评论。 GROUPING 在列因 ROLLUP 为空时返回 1,因此 GROUPING(country)GROUPING(province) 都可以工作。
    • @Mr.Llama:请考虑发布您的ROLLUP( (country, province) ) 解决方案作为答案。这在您的场景中非常有意义,我个人认为它比这个答案更清楚,无论我多么喜欢支持 ypercube 的帖子。
    • @ypercube - 取消删除我的答案并为GROUP BYROLLUP 方法添加了解释。
    • @SagarChaudhary 是的,只需添加另一个分组集。例如:GROUP BY GROUPING SETS ( (country, province), (country), () ); 也可以添加每个国家/地区的摘要。
    【解决方案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 Fiddle。

    【讨论】:

      【解决方案4】:

      我想出了一个使用 Union 将 Total 添加到结果末尾的 sql。你可以看到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;
      

      【讨论】:

        【解决方案5】:

        首先想到的是在应用rollup 之后过滤掉小计:

        SELECT *
        FROM   (SELECT   country, province, SUM (population)
                FROM     census
                GROUP BY ROLLUP (country, province))
        WHERE  province IS NOT NULL OR country IS NULL;
        

        您可以通过在 HAVING 子句中使用 GROUPING_ID 更紧凑地完成同样的事情:

        SELECT   country,
                 province,
                 SUM (population)
        FROM     census
        GROUP BY ROLLUP (country, province)
        HAVING   GROUPING_ID (country, province) <> 1
        

        而且,正如@Anssssss 所指出的,您还可以在HAVING 子句的第一个答案中使用WHERE 子句中的条件:

        SELECT   country, province, SUM (population)
        FROM     census
        GROUP BY ROLLUP (country, province)
        HAVING   province IS NOT NULL OR country IS NULL
        

        【讨论】:

          【解决方案6】:

          你可以使用联合:

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

          【讨论】:

          • 原发帖人说他们已经在做一个 UNION,效率低下而且“丑陋”。 SQL fiddle 详细说明了这一点。他们想避免建立工会。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-01-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多