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 聚合,因此效率更高。然后,您需要做的就是替换 TotalNULL 值,这样您就有了总行数。

我也会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,其原因在以下文章中得到了很好的总结:

这给你一个最终查询:'

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;