将递归CTE与窗口功能一起使用

时间:2020-03-11 17:33:41

标签: sql sql-server

我有一些数据想要在分组变量(n)中计算百分位(在Weekday上)。我可以使用while循环来完成此操作,但是正在尝试使用CTE。当我尝试转换为CTE时,会得到:

PERCENTILE_DISC函数的输入参数必须为常量。

如何在递归CTE中使用窗口函数?

将伪造的数据集作为临时表

IF(OBJECT_ID('tempdb..##TEMP') IS NOT NULL) BEGIN DROP TABLE ##TEMP END
;WITH cte_numbers(n, weekday) 
AS (
    SELECT 
        0, 
        DATENAME(DW, 0)
    UNION ALL
    SELECT    
        n + 1, 
        DATENAME(DW, n + 1)
    FROM    
        cte_numbers
    WHERE n < 1000

)

SELECT weekday, n INTO ##Temp
  FROM cte_numbers
  OPTION (maxrecursion 1000)

--  SELECT * FROM ##TEMP

同时使用循环解决方案

DECLARE @PercentileLookup TABLE(
    [Weekday] VARCHAR(250),
    [Percentile] INT,
    [n] INT
)

DECLARE @p INT;
SET @p=0;
WHILE @p < 101
BEGIN 
    INSERT INTO @PercentileLookup 
    SELECT DISTINCT
        frm.[Weekday],
        @p as Percentile,
        PERCENTILE_DISC(CAST(@p AS FLOAT)/100) WITHIN GROUP (ORDER BY frm.[n]) OVER(PARTITION BY frm.[Weekday]) as n
    FROM ##Temp as frm

    SET @p = @p + 1;
END;

SELECT * FROM @PercentileLookup
ORDER BY Weekday, n, Percentile

尝试使用递归CTE和错误消息

WITH PercentileLookup(Weekday, Percentile, n) AS (

    SELECT 
            frm.[Weekday],
            0,
            PERCENTILE_DISC(CAST(.0 AS FLOAT)/100) WITHIN GROUP (ORDER BY frm.[n]) OVER(PARTITION BY frm.[Weekday]) as n
        FROM ##Temp as frm

    UNION ALL

    SELECT 
            frm.[Weekday],
            Percentile + 1,
            PERCENTILE_DISC(CAST(Percentile AS FLOAT)/100) WITHIN GROUP (ORDER BY frm.[n]) OVER(PARTITION BY frm.[Weekday]) as n
        FROM PercentileLookup as pl
        INNER JOIN ##Temp as frm ON frm.[Weekday] = pl.[Weekday] 
        WHERE Percentile <= 100
)
SELECT DISTINCT * FROM PercentileLookup


--Msg 8726, Level 16, State 1, Line 70
--Input parameter of PERCENTILE_DISC function must be a constant.

--Completion time: 2020-03-11T13:26:16.4701034-04:00

1 个答案:

答案 0 :(得分:1)

您不能使用rCTE或Tally(更好的 )。如documentation所述(错误告诉您),第一个参数必须 literal ;列的值不是文字,因此无法使用。

可以使用动态语句来执行此操作,但这并不理想:

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);

WITH N AS(
    SELECT N
    FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
    SELECT CONVERT(decimal(3,0),0) AS I
    UNION ALL
    SELECT CONVERT(decimal(3,0),ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS I
    FROM N N1, N N2)
SELECT @SQL = STUFF((SELECT @CRLF + N'UNION' + @CRLF +
                           N'SELECT frm.[Weekday],'  + @CRLF +
                           N'       ' + FORMAT(T.I,'0') + N' AS Percentile,' + @CRLF +
                           N'       PERCENTILE_DISC(' + FORMAT(T.I/100,'0.00') + N') WITHIN GROUP (ORDER BY frm.[n]) OVER(PARTITION BY frm.[Weekday]) AS n' + @CRLF +
                           N'FROM ##Temp AS frm'
                    FROM Tally T
                    ORDER BY T.I
                    FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,9,N'') + @CRLF +
             N'ORDER BY Weekday, n, Percentile;'

--SELECT @SQL;

EXEC sys.sp_executesql @SQL;

DB<>Fiddle