CTE self join 减慢执行速度
CTE self join slow down the execution
我在 SP 中使用以下查询。
DECLARE @DateFrom datetime = '01/01/1753',
@DateTo datetime = '12/31/9999'
BEGIN
WITH tmpTethers
AS
(
SELECT TL.str_systemid AS SystemCode,
ISNULL(ml.name, ml.location) AS [System],
TL.dte_created AS [Date],
TL.str_LengthId AS TetherRegId,
0 AS LengthCut,
ISNULL(TL.dbl_newlength, 0) AS LengthAdded,
CAST(0 AS FLOAT) AS RemainingLength,
1 AS Mode,
UT.description AS UOM
FROM OP_TetherLength AS TL
INNER JOIN master_location AS ML ON ML.location = TL.str_systemid
LEFT JOIN udc_type AS UT ON TL.lng_lengthuom = UT.udc
WHERE (TL.dte_dateadded BETWEEN @DateFrom AND @DateTo)
UNION ALL
SELECT RR.systemcode AS SystemCode,
ISNULL(ML.name, ML.location) AS [System],
RR.datecreated AS [Date],
RR.oms_repairid AS TetherRegId,
ISNULL(RR.cutlength, 0) AS LengthCut,
0 AS LengthAdded,
0 AS RemainingLength,
0 AS Mode,
UT.description AS UOM
FROM Repair_Registration AS RR
INNER JOIN master_location AS ML ON RR.systemcode = ml.location
LEFT JOIN udc_type AS UT ON RR.cutlength_uomid = UT.udc
WHERE --RR.cut_umbilical_tether = 0 AND
RR.cutbackrequired = 1 AND
(RR.datecreated BETWEEN @DateFrom AND @DateTo)
),
tmpOrderedTethers
AS
(
SELECT TOP 1000
SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
RemainingLength,
Mode,
UOM,
ROW_NUMBER() OVER(PARTITION BY SystemCode ORDER BY [Date] ) AS RowNumber
FROM tmpTethers
ORDER BY SystemCode
),
tmpFinalTethers
AS
(
SELECT SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
CASE
WHEN Mode = 1 THEN LengthAdded
ELSE 0 - LengthCut
END AS RemainingLength,
Mode,
UOM,
RowNumber
FROM tmpOrderedTethers
WHERE RowNumber = 1
UNION ALL
SELECT tmpOT.SystemCode,
tmpOT.[System],
tmpOT.[Date],
tmpOT.TetherRegId,
tmpOT.LengthCut,
tmpOT.LengthAdded,
CASE
WHEN tmpOT.Mode = 1 THEN /*tmpFT.RemainingLength +*/ tmpOT.LengthAdded
ELSE tmpFT.RemainingLength - tmpOT.LengthCut
END AS RemainingLength,
CASE
WHEN tmpOT.Mode = 1 OR tmpFT.Mode = 1 THEN 1
ELSE 0
END AS Mode,
tmpOT.UOM,
tmpOT.RowNumber
FROM tmpOrderedTethers AS tmpOT
INNER JOIN tmpFinalTethers AS tmpFT ON tmpFT.SystemCode = tmpOT.SystemCode AND
tmpFT.RowNumber = tmpOT.RowNumber - 1
),
---- FT - Previous
---- OT - Current
SELECT SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
RemainingLength,
UOM,
RowNumber
,ROW_NUMBER() OVER(PARTITION BY SystemCode ORDER BY [Date] desc) AS SortNumber
FROM tmpGetFinalTethers
ORDER BY SystemCode, SortNumber
OPTION (MAXRECURSION 1000)
END
在上面的查询中,当我评论以下部分时,执行时间减少并且数据来得快:
SELECT tmpOT.SystemCode,
tmpOT.[System],
tmpOT.[Date],
tmpOT.TetherRegId,
tmpOT.LengthCut,
tmpOT.LengthAdded,
CASE
WHEN tmpOT.Mode = 1 THEN /*tmpFT.RemainingLength +*/ tmpOT.LengthAdded
ELSE tmpFT.RemainingLength - tmpOT.LengthCut
END AS RemainingLength,
CASE
WHEN tmpOT.Mode = 1 OR tmpFT.Mode = 1 THEN 1
ELSE 0
END AS Mode,
tmpOT.UOM,
tmpOT.RowNumber
FROM tmpOrderedTethers AS tmpOT
INNER JOIN tmpFinalTethers AS tmpFT ON tmpFT.SystemCode = tmpOT.SystemCode AND
tmpFT.RowNumber = tmpOT.RowNumber - 1
请告诉我如何改进它。
您的 [tmpFinalTethers]
和 [tmpGetFinalTethers]
cte 中似乎有逐行处理。
[tmpFinalTethers]
中返回的每一行都基于 [tmpOrderedTethers]
,而 [tmpOrderedTethers]
的数据基于 [tmpTethers]
。因此 [tmpOrderedTethers]
和 [tmpTethers]
中包含的逻辑将被执行 n 次,其中 n 是 [tmpFinalTethers]
.
返回的行数
原因是因为cte不是物化对象。它们不会存储在内存或光盘中,因此每次您在声明之外引用它们时它们都会执行。
如果您确实需要逐行处理您的任务并且没有其他选择,将 [tmpOrderedTethers]
的结果集加载到临时 table 可能会有所帮助。
而且你的[tmpFinalTethers]
和[tmpGetFinalTethers]
好像里面的逻辑是一样的。我不确定它的目的是什么。 Mb 你可以从 [tmpFinalTethers]
做最后的 select 并摆脱 [tmpGetFinalTethers]
.
已编辑:
像这样试试:
;WITH tmpTethers AS (...),
tmpOrderedTethers AS (...)
SELECT * INTO #tmpOrderedTethers FROM tmpOrderedTethers
;WITH tmpFinalTethers (
SELECT ... FROM #tmpOrderedTethers WHERE ...
UNION ALL
SELECT ... FROM #tmpOrderedTethers tmpOT INNER JOIN ...
)
已编辑 2:
因为你有 OPTION (MAXRECURSION 1000) 我想你总是得到 1000<= 行数。对于如此多的行,您使用递归 cte 结合 temp table 的解决方案可能会起作用。至少会比cursor好,因为它除了逐行处理外,还要消耗一些资源。但是如果你需要处理比方说 10 000 行,那么逐行处理绝对不是合适的解决方案,你应该找到另一个。
我在 SP 中使用以下查询。
DECLARE @DateFrom datetime = '01/01/1753',
@DateTo datetime = '12/31/9999'
BEGIN
WITH tmpTethers
AS
(
SELECT TL.str_systemid AS SystemCode,
ISNULL(ml.name, ml.location) AS [System],
TL.dte_created AS [Date],
TL.str_LengthId AS TetherRegId,
0 AS LengthCut,
ISNULL(TL.dbl_newlength, 0) AS LengthAdded,
CAST(0 AS FLOAT) AS RemainingLength,
1 AS Mode,
UT.description AS UOM
FROM OP_TetherLength AS TL
INNER JOIN master_location AS ML ON ML.location = TL.str_systemid
LEFT JOIN udc_type AS UT ON TL.lng_lengthuom = UT.udc
WHERE (TL.dte_dateadded BETWEEN @DateFrom AND @DateTo)
UNION ALL
SELECT RR.systemcode AS SystemCode,
ISNULL(ML.name, ML.location) AS [System],
RR.datecreated AS [Date],
RR.oms_repairid AS TetherRegId,
ISNULL(RR.cutlength, 0) AS LengthCut,
0 AS LengthAdded,
0 AS RemainingLength,
0 AS Mode,
UT.description AS UOM
FROM Repair_Registration AS RR
INNER JOIN master_location AS ML ON RR.systemcode = ml.location
LEFT JOIN udc_type AS UT ON RR.cutlength_uomid = UT.udc
WHERE --RR.cut_umbilical_tether = 0 AND
RR.cutbackrequired = 1 AND
(RR.datecreated BETWEEN @DateFrom AND @DateTo)
),
tmpOrderedTethers
AS
(
SELECT TOP 1000
SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
RemainingLength,
Mode,
UOM,
ROW_NUMBER() OVER(PARTITION BY SystemCode ORDER BY [Date] ) AS RowNumber
FROM tmpTethers
ORDER BY SystemCode
),
tmpFinalTethers
AS
(
SELECT SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
CASE
WHEN Mode = 1 THEN LengthAdded
ELSE 0 - LengthCut
END AS RemainingLength,
Mode,
UOM,
RowNumber
FROM tmpOrderedTethers
WHERE RowNumber = 1
UNION ALL
SELECT tmpOT.SystemCode,
tmpOT.[System],
tmpOT.[Date],
tmpOT.TetherRegId,
tmpOT.LengthCut,
tmpOT.LengthAdded,
CASE
WHEN tmpOT.Mode = 1 THEN /*tmpFT.RemainingLength +*/ tmpOT.LengthAdded
ELSE tmpFT.RemainingLength - tmpOT.LengthCut
END AS RemainingLength,
CASE
WHEN tmpOT.Mode = 1 OR tmpFT.Mode = 1 THEN 1
ELSE 0
END AS Mode,
tmpOT.UOM,
tmpOT.RowNumber
FROM tmpOrderedTethers AS tmpOT
INNER JOIN tmpFinalTethers AS tmpFT ON tmpFT.SystemCode = tmpOT.SystemCode AND
tmpFT.RowNumber = tmpOT.RowNumber - 1
),
---- FT - Previous
---- OT - Current
SELECT SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
RemainingLength,
UOM,
RowNumber
,ROW_NUMBER() OVER(PARTITION BY SystemCode ORDER BY [Date] desc) AS SortNumber
FROM tmpGetFinalTethers
ORDER BY SystemCode, SortNumber
OPTION (MAXRECURSION 1000)
END
在上面的查询中,当我评论以下部分时,执行时间减少并且数据来得快:
SELECT tmpOT.SystemCode,
tmpOT.[System],
tmpOT.[Date],
tmpOT.TetherRegId,
tmpOT.LengthCut,
tmpOT.LengthAdded,
CASE
WHEN tmpOT.Mode = 1 THEN /*tmpFT.RemainingLength +*/ tmpOT.LengthAdded
ELSE tmpFT.RemainingLength - tmpOT.LengthCut
END AS RemainingLength,
CASE
WHEN tmpOT.Mode = 1 OR tmpFT.Mode = 1 THEN 1
ELSE 0
END AS Mode,
tmpOT.UOM,
tmpOT.RowNumber
FROM tmpOrderedTethers AS tmpOT
INNER JOIN tmpFinalTethers AS tmpFT ON tmpFT.SystemCode = tmpOT.SystemCode AND
tmpFT.RowNumber = tmpOT.RowNumber - 1
请告诉我如何改进它。
您的 [tmpFinalTethers]
和 [tmpGetFinalTethers]
cte 中似乎有逐行处理。
[tmpFinalTethers]
中返回的每一行都基于 [tmpOrderedTethers]
,而 [tmpOrderedTethers]
的数据基于 [tmpTethers]
。因此 [tmpOrderedTethers]
和 [tmpTethers]
中包含的逻辑将被执行 n 次,其中 n 是 [tmpFinalTethers]
.
原因是因为cte不是物化对象。它们不会存储在内存或光盘中,因此每次您在声明之外引用它们时它们都会执行。
如果您确实需要逐行处理您的任务并且没有其他选择,将 [tmpOrderedTethers]
的结果集加载到临时 table 可能会有所帮助。
而且你的[tmpFinalTethers]
和[tmpGetFinalTethers]
好像里面的逻辑是一样的。我不确定它的目的是什么。 Mb 你可以从 [tmpFinalTethers]
做最后的 select 并摆脱 [tmpGetFinalTethers]
.
已编辑:
像这样试试:
;WITH tmpTethers AS (...),
tmpOrderedTethers AS (...)
SELECT * INTO #tmpOrderedTethers FROM tmpOrderedTethers
;WITH tmpFinalTethers (
SELECT ... FROM #tmpOrderedTethers WHERE ...
UNION ALL
SELECT ... FROM #tmpOrderedTethers tmpOT INNER JOIN ...
)
已编辑 2:
因为你有 OPTION (MAXRECURSION 1000) 我想你总是得到 1000<= 行数。对于如此多的行,您使用递归 cte 结合 temp table 的解决方案可能会起作用。至少会比cursor好,因为它除了逐行处理外,还要消耗一些资源。但是如果你需要处理比方说 10 000 行,那么逐行处理绝对不是合适的解决方案,你应该找到另一个。