如何使用 SQL 创建两个总计行 - 总计和平均值
How to create two Grand Total rows using SQL - Totals and Averages
我需要创建包含总计的两行,而不是典型的总计行。总计和平均值。
我正在使用基本 SQL 创建报告,我正在使用 Oracle 数据库,但我没有使用任何 PL/SQL。
我目前正在使用 Group By Grouping Sets 生成报告,其中一行是包含总计的行。这些总计目前正在使用 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),
()
)
;
我要找的图片示例。
以下是我关于如何获得这个额外总计行的想法,但不确定这是否是我应该做的以获得它的方法。看起来很复杂,我一直认为这应该是 Grouping Sets 的现有功能。在下面的方法中,我使用 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
;
CTE 是 window 函数,因此它们不能像您期望的那样被执行。对于这个问题,我认为你有一个好主意,但可能只是使用几个临时表来存放特定数据,然后最后将所有内容 UNION 在一起。
这是我提出的查询:
-- 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
您指出:
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
以及 [grouping-sets]
标签 的定义方式:
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.
原来你的方法这么好
我认为使用 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
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
;
我需要创建包含总计的两行,而不是典型的总计行。总计和平均值。
我正在使用基本 SQL 创建报告,我正在使用 Oracle 数据库,但我没有使用任何 PL/SQL。
我目前正在使用 Group By Grouping Sets 生成报告,其中一行是包含总计的行。这些总计目前正在使用 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),
()
)
;
我要找的图片示例。
以下是我关于如何获得这个额外总计行的想法,但不确定这是否是我应该做的以获得它的方法。看起来很复杂,我一直认为这应该是 Grouping Sets 的现有功能。在下面的方法中,我使用 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
;
CTE 是 window 函数,因此它们不能像您期望的那样被执行。对于这个问题,我认为你有一个好主意,但可能只是使用几个临时表来存放特定数据,然后最后将所有内容 UNION 在一起。
这是我提出的查询:
-- 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
您指出:
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
以及 [grouping-sets]
标签 的定义方式:
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.
原来你的方法这么好
我认为使用 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
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
;