如何使用SQL创建两个总计行-总计和平均值

时间:2019-01-08 18:01:01

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

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

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

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

以下是一些示例数据,这些数据使用“按分组集”生成“总计”。缺少的是我要生成平均值的“总计”下方的另一行。

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),
                  ()
                )
;

我要寻找的示例图像。

enter image description here

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

enter image description here

上面截图中的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
;

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

You pointed out :

I keep thinking that this should be an existing feature of Grouping Sets. In the below approach I'm using CTE's and UNION ALL to get the extra Averages Totals at the bottom of my data set as seen in the screenshot below

and How the [grouping-sets] tag is defined :

The GROUPING SETS operator is an extensions of the GROUP BY clause. It can generate the same result set as when you use UNION ALL to combine single grouping queries; however, using GROUPING SETS operator is usually more efficient.

Therefore, you had such a nice approach.

I think using GROUPING_ID suits best for your case as in the following SQL statement :

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

Update to SQL to work with any number or records aka clients

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
;