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];
算法如下:
- select 只有我们需要的行
- 计算每行months/3的差异
- 在助手中生成数字 table
- 使用
cross apply
生成新行
如果您有超过 2.5k 个号码,您可以使用 CROSS JOIN
to generate,但我在示例中将其删除。此外,我们使用 [master]..[spt_values]
来生成总周期。但这只是在 T-SQL 中生成数字的一种方法。您可以查看 link 并根据需要使用其他技术。
我们的想法是找到每个 [adm_date]
和 [disch_date]
之间的时间段 - 一个时间段以 3 个月为单位 - 这就是我们使用 DATEDIFF
和 [=18= 的原因] 然后除以 3。我们使用 ceiling 来获取上限值。当然,您可以生成比需要更多的句点,并在最终结果中用 WHERE
子句排除它们。
我有以下代码,(基于社区成员的帮助):
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];
算法如下:
- select 只有我们需要的行
- 计算每行months/3的差异
- 在助手中生成数字 table
- 使用
cross apply
生成新行
如果您有超过 2.5k 个号码,您可以使用 CROSS JOIN
to generate,但我在示例中将其删除。此外,我们使用 [master]..[spt_values]
来生成总周期。但这只是在 T-SQL 中生成数字的一种方法。您可以查看 link 并根据需要使用其他技术。
我们的想法是找到每个 [adm_date]
和 [disch_date]
之间的时间段 - 一个时间段以 3 个月为单位 - 这就是我们使用 DATEDIFF
和 [=18= 的原因] 然后除以 3。我们使用 ceiling 来获取上限值。当然,您可以生成比需要更多的句点,并在最终结果中用 WHERE
子句排除它们。