T-SQL 中的递归 CTE 性能缓慢

Slow performance on Recursive CTE in T-SQL

我有以下代码,(基于社区成员的帮助):

      use [Credible];

      WITH DataSource AS
      (
       SELECT
             ROW_NUMBER() OVER(ORDER BY epi.[client_id]) AS [row_id]
            ,epi.[client_id]
            ,CONVERT(date, epi.[admission_date]) AS [admission_date]
            ,CONVERT(date, DATEADD(MONTH, 3, epi.[admission_date])) AS [3Month Date]
            ,CONVERT(date, ISNULL(epi.[discharge_date], GETDATE())) AS [discharge_date]

       FROM 
           [dbo].[ClientEpisode] epi

       WHERE DATEADD(MONTH, 3, [admission_date]) <= ISNULL([discharge_date], GETDATE()) 
  ),
    RecursiveDataSource AS
     (
      SELECT 
            [row_id]
           ,[client_id]
           ,[admission_date]
           ,[3Month Date]
           ,[discharge_date]
           ,0 AS [level]

     FROM 
         DataSource

         UNION ALL

     SELECT 
          ds.[row_id]
         ,ds.[client_id]
         ,ds.[admission_date]
         ,DATEADD(MONTH, 3, rds.[3Month Date])
         ,ds.[discharge_date]
         ,[level] + 1

    FROM 
          RecursiveDataSource rds
          INNER JOIN DataSource ds ON 
          rds.[row_id] = ds.[row_id] AND DATEADD(MONTH, 3, rds.[3Month Date]) < ds.[discharge_date]
    )

     SELECT *

     FROM RecursiveDataSource 

     ORDER BY [row_id]
             ,[level]
     -- OPTION (MAXRECURSION 32767);

如果 table 中最多有 1000 条记录,此代码的运行速度约为 30 秒。

但是我的 table 有超过 14 000 条记录,而且还会增加, 代码工作 10+++ min

有没有办法让它在 30 秒左右的时间内表现出来?

感谢您的帮助

由于您在这里寻找性能解决方案,我建议您跳过使用 recursive CTE 并以其他方式解决问题。

例如,这个的变体:

DECLARE @DataSource TABLE
(
    [client_id] INT
   ,[adm_date] DATE
   ,[disch_date] DATE
);

INSERT INTO @DataSource ([client_id], [adm_date], [disch_date])
VALUES (1002, '3/11/2005 ', '5/2/2005')
      ,(1002, '8/30/2005 ', '2/16/2007')
      ,(1002, '3/16/2017 ', NULL);

DROP TABLE IF EXISTS #DataSource;
DROP TABLE IF EXISTS #DataNumers;

CREATE TABLE #DataSource
(
    [client_id] INT
   ,[adm_date] DATE
   ,[disch_date] DATE
   ,[diff_in_months] INT
);

CREATE TABLE #DataNumers
(
    [number] INT
);

DECLARE @max_diff_in_months INT;

INSERT INTO #DataSource ([client_id], [adm_date], [disch_date], [diff_in_months])
SELECT [client_id]
      ,[adm_date]
      ,ISNULL([disch_date], GETUTCDATE()) AS [disch_date]
      ,CEILING(DATEDIFF(MONTH, [adm_date], ISNULL([disch_date], GETUTCDATE())) * 1.0 / 3.0) - 1
FROM @DataSource
WHERE DATEADD(MONTH, 3, [adm_date]) <= ISNULL([disch_date], GETUTCDATE());

SELECT @max_diff_in_months = MAX([diff_in_months])
FROM #DataSource;

INSERT INTO #DataNumers ([number])
SELECT TOP (@max_diff_in_months) ROW_NUMBER() OVER(ORDER BY (SELECT 1))
FROM [master]..[spt_values];

SELECT DS.[client_id]
      ,DS.[adm_date]
      ,DATEADD(MONTH, DN.[number] * 3, [adm_date]) AS [3Month Date]
      ,DS.[disch_date]
FROM #DataSource DS
CROSS APPLY #DataNumers DN
WHERE DS.[diff_in_months] >= DN.[number];

算法如下:

  1. select 只有我们需要的行
  2. 计算每行months/3的差异
  3. 在助手中生成数字 table
  4. 使用 cross apply
  5. 生成新行

如果您有超过 2.5k 个号码,您可以使用 CROSS JOIN to generate,但我在示例中将其删除。此外,我们使用 [master]..[spt_values] 来生成总周期。但这只是在 T-SQL 中生成数字的一种方法。您可以查看 link 并根据需要使用其他技术。

我们的想法是找到每个 [adm_date][disch_date] 之间的时间段 - 一个时间段以 3 个月为单位 - 这就是我们使用 DATEDIFF 和 [=18= 的原因] 然后除以 3。我们使用 ceiling 来获取上限值。当然,您可以生成比需要更多的句点,并在最终结果中用 WHERE 子句排除它们。