SQL 服务器在 Dynamic Pivot 中添加总计行
SQL Server Add a total row in Dynamic Pivot
我有一个动态 Pivot
查询,如下所示:
DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)
DECLARE @start AS DateTime
DECLARE @end AS DateTime
DECLARE @business AS VARCHAR(50)
SET @start = '2015-01-01';
SET @end = '2015-12-01';
SET @business = 'EUR';
--Get distinct values of the PIVOT Column
SELECT @ColumnName= ISNULL(@ColumnName + ',','')
+ QUOTENAME(date1)
FROM (
SELECT m.date1, m.date2 FROM(
SELECT DISTINCT CONVERT(nvarchar(50), DATENAME(m, date)
+ ', '
+ DATENAME(yyyy,date)) as date1, date as date2
FROM bus_best where date between @start and @end
)m
)tab order by tab.date2
SET @DynamicPivotQuery =
'select * from (
select sum(bb.value) as value, bb.date as date, c.name as Name from bus_best bb
join pro p on p.id = bb.id
join con c on c.id = p.id
join bus_t bu on bu.id = c.id
where bb.date between '''+ cast (@start as VARCHAR(50))+''' and '''+ cast (@end as VARCHAR(50))+'''
and bu.name = '''+ cast (@business as VARCHAR(50))+'''
group by bb.date, c.name
) as t
PIVOT(SUM(t.value)
FOR date IN (' + @ColumnName + ')) AS PVTTable'
EXEC sp_executesql @DynamicPivotQuery
输出类似于:
Name Jan Feb March April May June July ....
----------------------------------------------------------
Name1 32 654 1 42 342 4 4543
Name2 54 3 234 43 453 432 22
Name3 55 12 56 1234 43 643 12
Name4 77 235 3566 35635 23 2 3462
我只想在底部添加最后一行,它将对所有行求和,例如:
Name Jan Feb March April May June July ....
----------------------------------------------------------
Name1 32 654 1 42 342 4 4543
Name2 54 3 234 43 453 432 22
Name3 55 12 56 1234 43 643 12
Name4 77 235 3566 35635 23 2 3462
Total ... .... .... .... .... .... .....
通过使用 GROUPING SETS
,您可以将总行数添加到子查询中,作为一个简单的示例,如果您有一个查询:
SELECT A, B, SUM(C) AS C
FROM T
GROUP BY A, B;
这给你:
A B C
-------------------
1 1 5
1 2 3
2 1 8
2 2 1
如果按如下方式使用分组集
SELECT A, B, SUM(C) AS C
FROM T
GROUP BY GROUPING SETS ((A, B), (A));
你得到
A B C
-------------------
1 1 5
1 2 3
1 NULL 8 -- Total for A = 1
2 1 8
2 2 1
2 NULL 9 -- Total for A = 2
这相当于:
SELECT A, B, SUM(C) AS C
FROM T
GROUP BY A, B
UNION ALL
SELECT A, NULL, SUM(C) AS C
FROM T
GROUP BY A;
所以每个分组集本质上代表一个进一步的查询,但在内部 SQL 服务器能够 re-use 聚合,因此效率更高。然后,您需要做的就是替换 Total
的 NULL
值,这样您就有了总行数。
我也会advise against variable concatenation (SELECT @Columnname = @ColumnName + SomeField FROM SomeTable
) since the results are not guaranteeed to be correct. Instead use XML extensions to concatenate your rows to columns.
此外,我会使用参数化查询,而不是:
DECLARE @Variable VARCHAR(10) = 'TEST';
SET @DynamicPivotQuery = 'SELECT * FROM T WHERE Column = ''' + @Variable + '''';
EXECUTE sp_executesql @DynamicPivotQuery;
改为使用:
DECLARE @Variable VARCHAR(10) = 'TEST';
SET @DynamicPivotQuery = 'SELECT * FROM T WHERE Column = @Param';
EXECUTE sp_executesql @DynamicPivotQuery, N'@Param VARCHAR(10)', @Param = @Variable;
这为您提供了正确键入的参数,因此无需将日期转换为 varchars 即可将它们添加到查询中,只是因为您的查询必须在执行时将它们转换回日期。
最后,我还没有更正这个问题,但我建议在处理日期时再次使用 BETWEEN
,其原因在以下文章中得到了很好的总结:
- What do BETWEEN and the devil have in common?
- Bad habits to kick : mis-handling date / range queries
这给你一个最终查询:'
DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)
DECLARE @start AS DATETIME
DECLARE @end AS DATETIME
DECLARE @business AS VARCHAR(50)
SET @start = '2015-01-01';
SET @end = '2015-12-01';
SET @business = 'EUR';
--Get distinct values of the PIVOT Column
-- Uses "DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)" to get the first of each
-- month then converts this to the format "yyyymmdd" (this is culture insensitive)
SET @ColumnName =
STUFF(( SELECT ',' + QUOTENAME(CONVERT(VARCHAR(10), D.[Date], 112))
FROM ( SELECT [Date] = DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
FROM bus_best
WHERE [Date] BETWEEN @start AND @end
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
) AS d
ORDER BY d.[Date]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
SET @DynamicPivotQuery =
'SELECT Name, ' + @ColumnName + '
FROM ( SELECT SUM(bb.value) AS Value,
Date = DATEADD(MONTH, DATEDIFF(MONTH, 0, bb.date), 0),
ISNULL(c.name, ''Total'') AS Name
FROM bus_best bb
INNER JOIN pro AS p ON p.id = bb.id
INNER JOIN con AS c ON c.id = p.id
INNER JOIN bus_t AS bu ON bu.id = c.id
WHERE bb.date BETWEEN @StartParam AND @EndParam
AND bu.name = @BusinessParam
GROUP BY GROUPING SETS
( (DATEADD(MONTH, DATEDIFF(MONTH, 0, bb.date), 0), c.name),
(DATEADD(MONTH, DATEDIFF(MONTH, 0, bb.date), 0))
)
) AS t
PIVOT
( SUM(t.value)
FOR date IN (' + @ColumnName + ')
) AS PVTTable;';
EXECUTE sp_executesql
@DynamicPivotQuery,
N'@StartParam DATETIME, @EndParam DATETIME, @BusinessParam VARCHAR(50)',
@StartParam = @Start,
@EndParam = @End,
@BusinessParam = @Business;
N.B。我没有对此进行全面测试,因为它需要创建 4 个表,我只能猜测其中的数据,但希望答案和链接中有足够的信息,如果有一些小的语法错误,可以让您走上正确的轨道
完整示例
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE #T;
CREATE TABLE #T
(
[Date] DATE,
Business VARCHAR(50),
Value INT,
Name VARCHAR(50)
);
INSERT #T (Date, Business, Value, Name)
VALUES
('20150601', 'EUR', 1, 'Group 1'),
('20150605', 'EUR', 12, 'Group 2'),
('20150605', 'EUR', 3, 'Group 3'),
('20150701', 'EUR', 2, 'Group 1'),
('20150708', 'EUR', 2, 'Group 2'),
('20150702', 'EUR', 7, 'Group 3'),
('20150703', 'AAA', 2, 'Group 1');
DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)
DECLARE @start AS DATETIME
DECLARE @end AS DATETIME
DECLARE @business AS VARCHAR(50)
SET @start = '2015-01-01';
SET @end = '2015-12-01';
SET @business = 'EUR';
--Get distinct values of the PIVOT Column
SET @ColumnName =
STUFF(( SELECT ',' + QUOTENAME(CONVERT(VARCHAR(10), D.[Date], 120))
FROM ( SELECT [Date] = DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
FROM #T
WHERE [Date] BETWEEN @start AND @end
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
) AS d
ORDER BY d.[Date]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
SET @DynamicPivotQuery =
'SELECT Name, ' + @ColumnName + '
FROM ( SELECT SUM(bb.value) AS Value,
Date = DATEADD(MONTH, DATEDIFF(MONTH, 0, bb.date), 0),
ISNULL(bb.name, ''Total'') AS Name
FROM #T AS bb
WHERE bb.date BETWEEN @StartParam AND @EndParam
AND bb.Business = @BusinessParam
GROUP BY GROUPING SETS ((bb.date, bb.name), (bb.Date))
) AS t
PIVOT
( SUM(t.value)
FOR date IN (' + @ColumnName + ')
) AS PVTTable;';
EXECUTE sp_executesql
@DynamicPivotQuery,
N'@StartParam DATETIME, @EndParam DATETIME, @BusinessParam VARCHAR(50)',
@StartParam = @Start,
@EndParam = @End,
@BusinessParam = @Business;
我有一个动态 Pivot
查询,如下所示:
DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)
DECLARE @start AS DateTime
DECLARE @end AS DateTime
DECLARE @business AS VARCHAR(50)
SET @start = '2015-01-01';
SET @end = '2015-12-01';
SET @business = 'EUR';
--Get distinct values of the PIVOT Column
SELECT @ColumnName= ISNULL(@ColumnName + ',','')
+ QUOTENAME(date1)
FROM (
SELECT m.date1, m.date2 FROM(
SELECT DISTINCT CONVERT(nvarchar(50), DATENAME(m, date)
+ ', '
+ DATENAME(yyyy,date)) as date1, date as date2
FROM bus_best where date between @start and @end
)m
)tab order by tab.date2
SET @DynamicPivotQuery =
'select * from (
select sum(bb.value) as value, bb.date as date, c.name as Name from bus_best bb
join pro p on p.id = bb.id
join con c on c.id = p.id
join bus_t bu on bu.id = c.id
where bb.date between '''+ cast (@start as VARCHAR(50))+''' and '''+ cast (@end as VARCHAR(50))+'''
and bu.name = '''+ cast (@business as VARCHAR(50))+'''
group by bb.date, c.name
) as t
PIVOT(SUM(t.value)
FOR date IN (' + @ColumnName + ')) AS PVTTable'
EXEC sp_executesql @DynamicPivotQuery
输出类似于:
Name Jan Feb March April May June July ....
----------------------------------------------------------
Name1 32 654 1 42 342 4 4543
Name2 54 3 234 43 453 432 22
Name3 55 12 56 1234 43 643 12
Name4 77 235 3566 35635 23 2 3462
我只想在底部添加最后一行,它将对所有行求和,例如:
Name Jan Feb March April May June July ....
----------------------------------------------------------
Name1 32 654 1 42 342 4 4543
Name2 54 3 234 43 453 432 22
Name3 55 12 56 1234 43 643 12
Name4 77 235 3566 35635 23 2 3462
Total ... .... .... .... .... .... .....
通过使用 GROUPING SETS
,您可以将总行数添加到子查询中,作为一个简单的示例,如果您有一个查询:
SELECT A, B, SUM(C) AS C
FROM T
GROUP BY A, B;
这给你:
A B C
-------------------
1 1 5
1 2 3
2 1 8
2 2 1
如果按如下方式使用分组集
SELECT A, B, SUM(C) AS C
FROM T
GROUP BY GROUPING SETS ((A, B), (A));
你得到
A B C
-------------------
1 1 5
1 2 3
1 NULL 8 -- Total for A = 1
2 1 8
2 2 1
2 NULL 9 -- Total for A = 2
这相当于:
SELECT A, B, SUM(C) AS C
FROM T
GROUP BY A, B
UNION ALL
SELECT A, NULL, SUM(C) AS C
FROM T
GROUP BY A;
所以每个分组集本质上代表一个进一步的查询,但在内部 SQL 服务器能够 re-use 聚合,因此效率更高。然后,您需要做的就是替换 Total
的 NULL
值,这样您就有了总行数。
我也会advise against variable concatenation (SELECT @Columnname = @ColumnName + SomeField FROM SomeTable
) since the results are not guaranteeed to be correct. Instead use XML extensions to concatenate your rows to columns.
此外,我会使用参数化查询,而不是:
DECLARE @Variable VARCHAR(10) = 'TEST';
SET @DynamicPivotQuery = 'SELECT * FROM T WHERE Column = ''' + @Variable + '''';
EXECUTE sp_executesql @DynamicPivotQuery;
改为使用:
DECLARE @Variable VARCHAR(10) = 'TEST';
SET @DynamicPivotQuery = 'SELECT * FROM T WHERE Column = @Param';
EXECUTE sp_executesql @DynamicPivotQuery, N'@Param VARCHAR(10)', @Param = @Variable;
这为您提供了正确键入的参数,因此无需将日期转换为 varchars 即可将它们添加到查询中,只是因为您的查询必须在执行时将它们转换回日期。
最后,我还没有更正这个问题,但我建议在处理日期时再次使用 BETWEEN
,其原因在以下文章中得到了很好的总结:
- What do BETWEEN and the devil have in common?
- Bad habits to kick : mis-handling date / range queries
这给你一个最终查询:'
DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)
DECLARE @start AS DATETIME
DECLARE @end AS DATETIME
DECLARE @business AS VARCHAR(50)
SET @start = '2015-01-01';
SET @end = '2015-12-01';
SET @business = 'EUR';
--Get distinct values of the PIVOT Column
-- Uses "DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)" to get the first of each
-- month then converts this to the format "yyyymmdd" (this is culture insensitive)
SET @ColumnName =
STUFF(( SELECT ',' + QUOTENAME(CONVERT(VARCHAR(10), D.[Date], 112))
FROM ( SELECT [Date] = DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
FROM bus_best
WHERE [Date] BETWEEN @start AND @end
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
) AS d
ORDER BY d.[Date]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
SET @DynamicPivotQuery =
'SELECT Name, ' + @ColumnName + '
FROM ( SELECT SUM(bb.value) AS Value,
Date = DATEADD(MONTH, DATEDIFF(MONTH, 0, bb.date), 0),
ISNULL(c.name, ''Total'') AS Name
FROM bus_best bb
INNER JOIN pro AS p ON p.id = bb.id
INNER JOIN con AS c ON c.id = p.id
INNER JOIN bus_t AS bu ON bu.id = c.id
WHERE bb.date BETWEEN @StartParam AND @EndParam
AND bu.name = @BusinessParam
GROUP BY GROUPING SETS
( (DATEADD(MONTH, DATEDIFF(MONTH, 0, bb.date), 0), c.name),
(DATEADD(MONTH, DATEDIFF(MONTH, 0, bb.date), 0))
)
) AS t
PIVOT
( SUM(t.value)
FOR date IN (' + @ColumnName + ')
) AS PVTTable;';
EXECUTE sp_executesql
@DynamicPivotQuery,
N'@StartParam DATETIME, @EndParam DATETIME, @BusinessParam VARCHAR(50)',
@StartParam = @Start,
@EndParam = @End,
@BusinessParam = @Business;
N.B。我没有对此进行全面测试,因为它需要创建 4 个表,我只能猜测其中的数据,但希望答案和链接中有足够的信息,如果有一些小的语法错误,可以让您走上正确的轨道
完整示例
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE #T;
CREATE TABLE #T
(
[Date] DATE,
Business VARCHAR(50),
Value INT,
Name VARCHAR(50)
);
INSERT #T (Date, Business, Value, Name)
VALUES
('20150601', 'EUR', 1, 'Group 1'),
('20150605', 'EUR', 12, 'Group 2'),
('20150605', 'EUR', 3, 'Group 3'),
('20150701', 'EUR', 2, 'Group 1'),
('20150708', 'EUR', 2, 'Group 2'),
('20150702', 'EUR', 7, 'Group 3'),
('20150703', 'AAA', 2, 'Group 1');
DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)
DECLARE @start AS DATETIME
DECLARE @end AS DATETIME
DECLARE @business AS VARCHAR(50)
SET @start = '2015-01-01';
SET @end = '2015-12-01';
SET @business = 'EUR';
--Get distinct values of the PIVOT Column
SET @ColumnName =
STUFF(( SELECT ',' + QUOTENAME(CONVERT(VARCHAR(10), D.[Date], 120))
FROM ( SELECT [Date] = DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
FROM #T
WHERE [Date] BETWEEN @start AND @end
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
) AS d
ORDER BY d.[Date]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
SET @DynamicPivotQuery =
'SELECT Name, ' + @ColumnName + '
FROM ( SELECT SUM(bb.value) AS Value,
Date = DATEADD(MONTH, DATEDIFF(MONTH, 0, bb.date), 0),
ISNULL(bb.name, ''Total'') AS Name
FROM #T AS bb
WHERE bb.date BETWEEN @StartParam AND @EndParam
AND bb.Business = @BusinessParam
GROUP BY GROUPING SETS ((bb.date, bb.name), (bb.Date))
) AS t
PIVOT
( SUM(t.value)
FOR date IN (' + @ColumnName + ')
) AS PVTTable;';
EXECUTE sp_executesql
@DynamicPivotQuery,
N'@StartParam DATETIME, @EndParam DATETIME, @BusinessParam VARCHAR(50)',
@StartParam = @Start,
@EndParam = @End,
@BusinessParam = @Business;