【问题标题】:How to create two Grand Total rows using SQL - Totals and Averages如何使用 SQL 创建两个总计行 - 总计和平均值
【发布时间】:2019-06-03 11:14:30
【问题描述】:

我需要创建两行包含总计,而不是典型的总计行。总计和平均值。

我正在使用基本 SQL 创建报告,并且正在使用 Oracle 数据库,但我没有使用任何 PL/SQL。

我目前正在使用 Group By Grouping Sets 来生成报告,其中一行是包含总计的行。这些总计目前正在使用 SUM(column) 生成,使用聚合和分析函数的组合来生成我的一行总计。我需要的是在同一数据集上产生总计的另一行。实现这一目标的最佳方法是什么?当我说得最好时,我正在考虑我的数据库上的负载,因为此报告将针对大量数据运行。我的示例非常基本,但可以理解重点。

以下是一些使用 Group By Grouping Sets 生成总计总计的示例数据。缺少的是我想要生成平均值的总计下方的另一行。

WITH sample_data AS
(
  SELECT 1 AS client_key, 'NASA'   AS client, 8 AS SPACESHIPS_SOLD, 105585 AS REVENUE FROM DUAL UNION ALL
  SELECT 2 AS client_key, 'Origin' AS client, 3 AS SPACESHIPS_SOLD, 36581  AS REVENUE FROM DUAL UNION ALL
  SELECT 3 AS client_key, 'SpaceX' AS client, 7 AS SPACESHIPS_SOLD, 83851  AS REVENUE FROM DUAL
)

SELECT sd.client_key
  , CASE WHEN grouping(sd.client) = 0 THEN to_char(sd.client) ELSE 'Grand Totals -->' END AS client
  , SUM(sd.spaceships_sold) AS spaceships_sold
  , SUM(sd.revenue)         AS revenue
FROM sample_data sd
GROUP BY 
  GROUPING SETS (
                  (sd.client_key, sd.client),
                  ()
                )
;

我正在寻找的示例图片。

以下是我对如何获得这个额外的总计行的想法,但不确定我是否应该这样做才能获得这个。似乎令人费解,我一直认为这应该是分组集的现有功能。在下面的方法中,我使用 CTE 和 UNION ALL 在我的数据集底部获取额外的平均总计,如下面的屏幕截图所示。

上面截图中的 SQL。

WITH sample_data AS
(
  SELECT 1 AS client_key, 'NASA'   AS client, 8 AS SPACESHIPS_SOLD, 105585 AS REVENUE FROM DUAL UNION ALL
  SELECT 2 AS client_key, 'Origin' AS client, 3 AS SPACESHIPS_SOLD, 36581  AS REVENUE FROM DUAL UNION ALL
  SELECT 3 AS client_key, 'SpaceX' AS client, 7 AS SPACESHIPS_SOLD, 83851  AS REVENUE FROM DUAL
)

, data_Sum_totals AS
(
  SELECT sd.client_key
    , CASE WHEN grouping(sd.client) = 0 THEN to_char(sd.client) ELSE 'Grand Totals -->' END AS client
    , SUM(sd.spaceships_sold) AS spaceships_sold
    , SUM(sd.revenue)         AS revenue
  FROM sample_data sd
  GROUP BY 
    GROUPING SETS (
                    (sd.client_key, sd.client),
                    ()
                  )
)

, data_Avg_totals AS
(
  SELECT grouping(sd.client_key) AS row_group
    , sd.client_key
    , CASE WHEN grouping(sd.client) = 0 THEN to_char(sd.client) ELSE 'AVG Totals -->' END AS client
    , AVG(sd.spaceships_sold) AS spaceships_sold
    , AVG(sd.revenue)         AS revenue
  FROM sample_data sd
  GROUP BY 
    GROUPING SETS (
                    (sd.client_key, sd.client),
                    ()
                  )
  HAVING grouping(sd.client_key) = 1 /* This line restricts the output to only give me the Totals row */
)

SELECT client_key, client, spaceships_sold, revenue
FROM data_Sum_totals
  UNION ALL
SELECT client_key, client, spaceships_sold, revenue
FROM data_Avg_totals
;

【问题讨论】:

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


    【解决方案1】:

    CTE 是窗口函数,因此它们无法达到您的预期。对于这个问题,我认为您有一个好主意,但可能只使用几个临时表来存放特定数据,然后最后将所有内容联合起来。

    这是我想出的查询:

    -- Clear out temporary tables
    IF OBJECT_ID('tempdb.dbo.#SampleData') IS NOT NULL DROP TABLE #SampleData
    IF OBJECT_ID('tempdb.dbo.#TotTable') IS NOT NULL DROP TABLE #TotTable
    IF OBJECT_ID('tempdb.dbo.#AvgTable') IS NOT NULL DROP TABLE #AvgTable
    
    -- Create
    DECLARE @_tot INT
    DECLARE @_avg NUMERIC(18,2)
    DECLARE @client_count INT
    
    -- Sample Data
    CREATE TABLE #SampleData (
        [CLIENT_KEY] INT,
        [CLIENT] NVARCHAR(10),
        [SPACESHIPS_SOLD] VARCHAR(10),
        [REVENUE] VARCHAR(25)
    )
    
    INSERT INTO #SampleData
    VALUES (1,'NASA','8','105585'),
            (2,'Origin','3','36581'),
            (3,'SpaceX','7','83851')
    
    
    -- Get our total numbers
    SELECT 'Grand Totals' AS [Name],
    SUM(CONVERT(INT, [REVENUE])) AS [Total_Rev],
    SUM(CONVERT(INT, [SPACESHIPS_SOLD])) AS [Ships_Sold] 
    INTO #TotTable
    FROM #SampleData
    
    -- Get our average numbers
    SET @client_count = (SELECT COUNT([CLIENT]) FROM #SampleData)
    SELECT 'AVG Totals' AS [Name],
    SUM(CONVERT(INT, [REVENUE])) / COUNT(*) AS [Avg_Rev],
    SUM(CONVERT(INT, [SPACESHIPS_SOLD])) / @client_count AS [Avg_Sold]
    INTO #AvgTable
    FROM #SampleData
    
    -- Union it all together
    SELECT
        [CLIENT_KEY],
        [CLIENT],
        [SPACESHIPS_SOLD],
        [REVENUE]
    FROM #SampleData
    UNION ALL
    SELECT
        NULL AS [CLIENT_KEY],
        [Name] AS [CLIENT],
        [Ships_Sold]  [SPACESHIPS_SOLD],
        [Total_Rev] AS [REVENUE]
    FROM #TotTable
    UNION ALL
    SELECT
        NULL AS [CLIENT_KEY],
        [Name] AS [CLIENT],
        [Avg_Sold]  [SPACESHIPS_SOLD],
        [Avg_Rev] AS [REVENUE]
    FROM #AvgTable
    
    --Clear out tables (not necessary, but nice to do)
    IF OBJECT_ID('tempdb.dbo.#SampleData') IS NOT NULL DROP TABLE #SampleData
    IF OBJECT_ID('tempdb.dbo.#TotTable') IS NOT NULL DROP TABLE #TotTable
    IF OBJECT_ID('tempdb.dbo.#AvgTable') IS NOT NULL DROP TABLE #AvgTable
    

    【讨论】:

    • 非常整洁,但是我在 oracle 中,无法像使用 SQL Server 访问临时表那样访问临时表。我相信您发布的内容从外观上看似乎是 MS SQL Server。无论如何,感谢您的解决方案。我也仅限于使用普通的旧 Oracle SQL。我可以使用 DML,但不能使用任何 DDL 功能。
    【解决方案2】:

    你指出:

    我一直认为这应该是 Grouping 的现有功能 套装。在下面的方法中,我使用 CTEUNION ALL 来获取 在我的数据集底部有额外的平均总数,如在中所示 截图如下

    以及如何定义[grouping-sets] 标签

    GROUPING SETS 运算符是GROUP BY 子句的扩展。它 可以生成与使用 UNION ALL 组合时相同的结果集 单个分组查询;但是,使用 GROUPING SETS 运算符是 通常更高效

    因此,你有这么好的方法。

    我认为使用GROUPING_ID 最适合您的情况,如以下 SQL 语句:

    SELECT client_key, 
           CASE WHEN flag = 3 THEN 'AVG Totals -.->' 
                WHEN flag = 2 THEN 'Grand Totals -.->'
                ELSE client 
            END AS client , 
           SUM(spaceships_sold)/ DECODE(flag,3,3,1) AS spaceships_sold, 
           SUM(revenue)/ DECODE(flag,3,3,1) AS revenue
      FROM
      (
        WITH sample_data AS
        (
         SELECT 1 AS client_key, 'NASA'   AS client, 8 AS SPACESHIPS_SOLD, 105585 AS REVENUE FROM DUAL 
         UNION ALL
         SELECT 2 AS client_key, 'Origin' AS client, 3 AS SPACESHIPS_SOLD, 36581  AS REVENUE FROM DUAL 
         UNION ALL
         SELECT 3 AS client_key, 'SpaceX' AS client, 7 AS SPACESHIPS_SOLD, 83851  AS REVENUE FROM DUAL
         )
          SELECT sd.client_key, 
                 nvl2(sd.client_key,client,null) AS client
               , SUM(sd.spaceships_sold) AS spaceships_sold
               , SUM(sd.revenue)         AS revenue
               , GROUPING_ID(sd.client_key, sd.client) AS flag
            FROM sample_data sd
           GROUP BY 
          GROUPING SETS (
                          (sd.client_key, sd.client),
                           (sd.client),()
                          )
        )    
      GROUP BY client_key, flag, client
      ORDER BY client_key, revenue desc;
    
    
    
      CLIENT_KEY    CLIENT           SPACESHIPS_SOLD    REVENUE
      -----------   ---------------- ---------------   --------
           1        NASA                   8             105585
           2        Origin                 3              36581
           3        SpaceX                 7              83851
          NULL      Grand Totals -.->     18             226017
          NULL      AVG Totals -.->        6              75339
    

    Rextester Demo

    更新到 SQL 以处理任何数量或记录的客户端

    SELECT client_key, 
         CASE WHEN flag = 3 THEN 'AVG Totals -->' 
              WHEN flag = 2 THEN 'Grand Totals -->'
              ELSE client 
          END AS client 
          , flag,
         SUM(spaceships_sold)/ DECODE(flag,3,tot_clients,1) AS spaceships_sold, 
         SUM(revenue)/ DECODE(flag,3,tot_clients,1) AS revenue
    FROM
    (
      WITH sample_data AS
      (
         SELECT 1 AS client_key, 'NASA'   AS client, 8  AS SPACESHIPS_SOLD, 105585  AS REVENUE FROM DUAL 
         UNION ALL
         SELECT 2 AS client_key, 'Origin' AS client, 3  AS SPACESHIPS_SOLD, 36581   AS REVENUE FROM DUAL 
         UNION ALL
         SELECT 3 AS client_key, 'SpaceX' AS client, 7  AS SPACESHIPS_SOLD, 83851   AS REVENUE FROM DUAL
         UNION ALL
         SELECT 4 AS client_key, 'Comp'   AS client, 4  AS SPACESHIPS_SOLD, 95823   AS REVENUE FROM DUAL
         UNION ALL
         SELECT 4 AS client_key, 'CNSA'   AS client, 11 AS SPACESHIPS_SOLD, 135851  AS REVENUE FROM DUAL
       )
        SELECT sd.client_key, 
               nvl2(sd.client_key,client,null) AS client
             , SUM(sd.spaceships_sold) AS spaceships_sold
             , SUM(sd.revenue)         AS revenue
             , COUNT(sd.client_key)    AS tot_clients
             , GROUPING_ID(sd.client_key, sd.client) AS flag
          FROM sample_data sd
         GROUP BY 
        GROUPING SETS (
                        (sd.client_key, sd.client),
                         (sd.client),()
                        )
      )    
    GROUP BY client_key, flag, client, tot_clients
    ORDER BY client_key, revenue desc
    ;
    

    【讨论】:

    • 哇...我花了一点时间才弄清楚它是如何产生平均值的。使用 DECODE 非常巧妙的“技巧”,但是对于少于或多于 3 条记录的任何选择或参考样本数据的客户端数量,在 Decode 中硬编码 3 失败。您的回答非常接近,但它确实让我为我的问题想出了一个更好的解决方案。我没有在解码中对 3 进行硬编码,而是使用 COUNT(sd.client_key) AS tot_clients 生成记录总数,并通过替换 3. DECODE(flag,3,tot_clients,1) AS 收入来使用 tot_clients 作为除数。
    • @CodeNovice 不错。
    • 我继续将我的新 SQL 添加到您的答案中,然后将您的帖子标记为答案。感谢您提供非常有趣的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多