问题描述
我在使带有数据透视列的表在 MS sql Server 上更具动态性时遇到问题。
我有一个查询从当前年份的五周时间段和前一年的五周时间段中检索数据,因此列 CUR_YR 和 PREV_YR 是来自数据透视子句的数据。这是我的查询:
DECLARE @TODAY DATE
SET @TODAY= CAST(GETDATE() AS DATE)
DECLARE @PREV_YR INT,@CUR_YR INT
SET @CUR_YR = YEAR(@TODAY)
SET @PREV_YR = @CUR_YR-1
SELECT * FROM (
SELECT BUS,DET,[STR],COALESCE(@PREV_YR,0) AS PREV_YR,COALESCE(@CUR_YR,0) AS CUR_YR FROM (
SELECT YR,BUS,count(ORD_ID_SE) as ORDS FROM (<SUBQUERY>) )AS T
PIVOT(
SUM(ORDS)
FOR YR IN (
@PREV_YR,@CUR_YR)
) AS pivot_table
)AS F
当我手动将 FOR YR IN() 子句中的枢轴列设置为 2020 和 2021 时,查询有效,但由于我需要这些列是动态的(也就是让它们随着时间的推移而改变,所以接下来年份是 2021 和 2022)我使用变量@PREV_YR 和@CUR_YR,如代码所示。但是,这会导致错误“'@PREV_YR' 附近的语法不正确。”。
有解决办法吗?也许用不同的方式来满足 IN() 子句中的两年。
感谢您的帮助。
解决方法
这是 PIVOT 功能的一个已知限制。您的 IN
列表中必须有一组预定义的列。它们不能动态改变。因此,为了做到这一点,您需要利用动态 SQL 根据您的输入值更改结果集。
有很多关于如何执行此操作的指南,但我将举例说明如何将其应用于脚本(添加了一些小的重新格式化)。为了运行它,我确实纠正了两个问题。 1.为您的子查询添加别名subquery_alias
2.将GROUP BY
子句添加到您的聚合查询
DECLARE @TODAY DATE = CAST(GETDATE() AS DATE);
DECLARE @CUR_YR INT = YEAR(@TODAY);
DECLARE @PREV_YR INT = @CUR_YR-1;
DECLARE @Sql nvarchar(max) =
N'SELECT
*
FROM
(SELECT
BUS,DET,[STR],COALESCE(' + QUOTENAME(CAST(@PREV_YR AS nvarchar(10))) + N',0) AS PREV_YR,COALESCE(' + QUOTENAME(CAST(@CUR_YR AS nvarchar(10))) + N',0) AS CUR_YR
FROM
(SELECT
YR,BUS,COUNT(ORD_ID_SE) AS ORDS
FROM
(<SUBQUERY>) AS subquery_alias
GROUP BY
YR,[STR]) AS T
PIVOT(SUM(ORDS) FOR YR IN (' + QUOTENAME(CAST(@PREV_YR AS nvarchar(10))) + N',' + QUOTENAME(CAST(@CUR_YR AS nvarchar(10))) + N')) AS pivot_table) AS F';
EXEC sys.sp_executesql @Sql;
话虽如此,在这种情况下,您将数据透视表的输出分别重新别名为 PREV_YR
和 CUR_YR
。所以你真的不需要动态列名,如果你只打算以这种方式使用两个不同的变量,那么使用 SUM(CASE WHEN....END)
方法旋转它更有意义。像这样:
DECLARE @TODAY DATE = CAST(GETDATE() AS DATE);
DECLARE @CUR_YR INT = YEAR(@TODAY);
DECLARE @PREV_YR INT = @CUR_YR-1;
SELECT
BUS,SUM(CASE WHEN YR = @PREV_YR THEN 1 ELSE 0 END) AS PREV_YR,SUM(CASE WHEN YR = @CUR_YR THEN 1 ELSE 0 END) AS CUR_YR
FROM
(<SUBQUERY>) AS subquery_alias
GROUP BY
BUS,[STR];
这样做可以完全避免动态 SQL 和更复杂的数据透视查询。