带有 CTE 的 MERGE 语句中的 OPTION (MAXRECURSION X) 不起作用?

OPTION (MAXRECURSION X) in MERGE Statement with CTE not working?

我有以下 CTE(Microsoft SQL Server 2017)按预期工作:

WITH DATE_CTE (Datum)
as
(
    SELECT DATEFROMPARTS(1970, 1, 1)
    UNION ALL
    SELECT DATEADD(Day, 1, DATE_CTE.Datum) FROM DATE_CTE WHERE YEAR(DATE_CTE.Datum) < 2100 
) 
SELECT DATE_CTE.Datum, 
        YEAR(DATE_CTE.Datum) as [Year], 
        MONTH(DATE_CTE.Datum) as [Month], 
        DATEPART(ISO_WEEK, DATE_CTE.Datum) as IsoWeek, 
        CAST(YEAR(DATE_CTE.Datum) AS VARCHAR(4)) + IIF(MONTH(DATE_CTE.Datum) < 10, '0' + CAST(MONTH(DATE_CTE.Datum) AS VARCHAR(1)), CAST(MONTH(DATE_CTE.Datum) AS VARCHAR(2))) as YearMonth, 
        -1 as SelID,  
        CASE WHEN MONTH(DATE_CTE.Datum) = 1 AND DATEPART(ISO_WEEK, DATE_CTE.Datum) >= 52 THEN YEAR(DATE_CTE.Datum)-1 WHEN MONTH(DATE_CTE.Datum) = 12 AND DATEPART(ISO_WEEK, DATE_CTE.Datum) = 1 THEN YEAR(DATE_CTE.Datum)+1 ELSE YEAR(DATE_CTE.Datum) END as WeekYear
FROM DATE_CTE OPTION (MAXRECURSION 0)

这将return以下数据(从 1970 到 2100 -> 47483 数据行):

    Date       Year        Month       IsoWeek     YearMonth SelID       WeekYear
    ---------- ----------- ----------- ----------- --------- ----------- -----------
    1970-01-01 1970        1           1           197001    -1          1970
    1970-01-02 1970        1           1           197001    -1          1970
    1970-01-03 1970        1           1           197001    -1          1970
    1970-01-04 1970        1           1           197001    -1          1970
    1970-01-05 1970        1           2           197001    -1          1970
    1970-01-06 1970        1           2           197001    -1          1970
    1970-01-07 1970        1           2           197001    -1          1970
    1970-01-08 1970        1           2           197001    -1          1970
    1970-01-09 1970        1           2           197001    -1          1970
    1970-01-10 1970        1           2           197001    -1          1970
    ... 

现在我想将数据保存在特定的 table 中,其中可能已经有一些数据(甚至更糟:可能有一些额外的字段)。所以我的想法是使用这样的合并语句:

WITH DATE_CTE (Datum)
as
(
    SELECT DATEFROMPARTS(1970, 1, 1)
    UNION ALL
    SELECT DATEADD(Day, 1, DATE_CTE.Datum) FROM DATE_CTE WHERE YEAR(DATE_CTE.Datum) < 2100 
) 
MERGE INTO SYS_LIST_DATEHLP AS Target  
USING (
        SELECT 
                DATE_CTE.Datum, 
                YEAR(DATE_CTE.Datum), 
                MONTH(DATE_CTE.Datum), 
                DATEPART(ISO_WEEK, DATE_CTE.Datum), 
                CAST(YEAR(DATE_CTE.Datum) AS VARCHAR(4)) + IIF(MONTH(DATE_CTE.Datum) < 10, '0' + CAST(MONTH(DATE_CTE.Datum) AS VARCHAR(1)), 
                CAST(MONTH(DATE_CTE.Datum) AS VARCHAR(2))), 
                -1, 
                CASE WHEN MONTH(DATE_CTE.Datum) = 1 AND DATEPART(ISO_WEEK, DATE_CTE.Datum) >= 52 THEN YEAR(DATE_CTE.Datum)-1 WHEN MONTH(DATE_CTE.Datum) = 12 AND DATEPART(ISO_WEEK, DATE_CTE.Datum) = 1 THEN YEAR(DATE_CTE.Datum)+1 ELSE YEAR(DATE_CTE.Datum) END
        FROM DATE_CTE OPTION (MAXRECURSION 0)
       )  
       AS Source (SYS_DATE, SYS_YEAR, SYS_MONTH, [WEEK], KAPMONAT, SEL_ID, WEEKYEAR)  
    ON Target.SYS_DATE = Source.SYS_DATE  
WHEN MATCHED THEN  
    UPDATE SET SYS_YEAR = Source.SYS_YEAR, SYS_MONTH = Source.SYS_MONTH, [WEEK] = Source.[WEEK], KAPMONAT = Source.KAPMONAT, SEL_ID = Source.SEL_ID, WEEKYEAR = Source.WEEKYEAR
WHEN NOT MATCHED BY TARGET THEN  
    INSERT (SYS_DATE, SYS_YEAR, SYS_MONTH, [WEEK], KAPMONAT, SEL_ID, WEEKYEAR)
    VALUES (Source.SYS_DATE, Source.SYS_YEAR, Source.SYS_MONTH, Source.[WEEK], Source.KAPMONAT, Source.SEL_ID, Source.WEEKYEAR);

但这总是失败并出现以下错误:

Incorrect syntax near the keyword 'OPTION'.

遗漏 OPTION (MAXRECURSION 0) 不是一个选项,因为 CTE 进行了 100 多次递归,然后 SQL 只是响应:

The statement terminated. The maximum recursion 100 has been exhausted before statement completion.

我必须在哪里设置OPTION (MAXRECURSION 0)?这有可能吗?遗憾的是,我没有在 Microsoft 文档中找到任何关于此 "special" 案例的提及。

您可以使用具有 CTE 结构的 table 变量来存储稍后将在 merge 语句中使用的结果:

declare @tmp table (
                        [Datum]     date, 
                        [Year]      int, 
                        [Month]     int,
                        [IsoWeek]   int, 
                        [YearMonth] int, 
                        [SelID]     int, 
                        [WeekYear]  int
                    )

然后您可以通过 MAXRECURSION 0 将结果写入 table 变量来执行 CTE:

WITH DATE_CTE (Datum)
as
(
    SELECT DATEFROMPARTS(1970, 1, 1)
    UNION ALL
    SELECT DATEADD(Day, 1, DATE_CTE.Datum) FROM DATE_CTE WHERE YEAR(DATE_CTE.Datum) < 2100 
) 
insert into @tmp
SELECT DATE_CTE.Datum, 
        YEAR(DATE_CTE.Datum) as [Year], 
        MONTH(DATE_CTE.Datum) as [Month], 
        DATEPART(ISO_WEEK, DATE_CTE.Datum) as IsoWeek, 
        CAST(YEAR(DATE_CTE.Datum) AS VARCHAR(4)) + IIF(MONTH(DATE_CTE.Datum) < 10, '0' + CAST(MONTH(DATE_CTE.Datum) AS VARCHAR(1)), CAST(MONTH(DATE_CTE.Datum) AS VARCHAR(2))) as YearMonth, 
        -1 as SelID,  
        CASE WHEN MONTH(DATE_CTE.Datum) = 1 AND DATEPART(ISO_WEEK, DATE_CTE.Datum) >= 52 THEN YEAR(DATE_CTE.Datum)-1 WHEN MONTH(DATE_CTE.Datum) = 12 AND DATEPART(ISO_WEEK, DATE_CTE.Datum) = 1 THEN YEAR(DATE_CTE.Datum)+1 ELSE YEAR(DATE_CTE.Datum) END as WeekYear
FROM DATE_CTE OPTION (MAXRECURSION 0)

现在您应该可以将 table SYS_LIST_DATEHLP 与 table 变量合并 @tmp:

MERGE INTO SYS_LIST_DATEHLP AS Target  
USING (
        SELECT 
             [Datum]     
            ,[Year]     
            ,[Month]    
            ,[IsoWeek]  
            ,[YearMonth]
            ,[SelID]    
            ,[WeekYear] 
        FROM @tmp  
       )  
       AS Source (SYS_DATE, SYS_YEAR, SYS_MONTH, [WEEK], KAPMONAT, SEL_ID, WEEKYEAR)  
    ON Target.SYS_DATE = Source.SYS_DATE  
WHEN MATCHED THEN  
    UPDATE SET SYS_YEAR = Source.SYS_YEAR, SYS_MONTH = Source.SYS_MONTH, [WEEK] = Source.[WEEK], KAPMONAT = Source.KAPMONAT, SEL_ID = Source.SEL_ID, WEEKYEAR = Source.WEEKYEAR
WHEN NOT MATCHED BY TARGET THEN  
    INSERT (SYS_DATE, SYS_YEAR, SYS_MONTH, [WEEK], KAPMONAT, SEL_ID, WEEKYEAR)
    VALUES (Source.SYS_DATE, Source.SYS_YEAR, Source.SYS_MONTH, Source.[WEEK], Source.KAPMONAT, Source.SEL_ID, Source.WEEKYEAR);

查询提示总是出现在查询的最后。所以只需将 OPTION (MAXRECURSION 0) 移到最后一个分号之前。

/* Rest of query omitted*/
WHEN NOT MATCHED BY TARGET THEN  
    INSERT (SYS_DATE, SYS_YEAR, SYS_MONTH, [WEEK], KAPMONAT, SEL_ID, WEEKYEAR)
    VALUES (Source.SYS_DATE, Source.SYS_YEAR, Source.SYS_MONTH, Source.[WEEK], Source.KAPMONAT, Source.SEL_ID, Source.WEEKYEAR)
     OPTION (MAXRECURSION 0)
    ;