Ken*_*her 28 sql-server sql-server-2008-r2
我正在尝试提高以下查询的性能:
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupId)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
) r ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
Run Code Online (Sandbox Code Playgroud)
目前我的测试数据大约需要一分钟。我对这个查询所在的整个存储过程的更改的输入量有限,但我可能会让他们修改这个查询。或者添加索引。我尝试添加以下索引:
CREATE CLUSTERED INDEX ix_test ON #TempTable(AgentID, RuleId, GroupId, Passed)
Run Code Online (Sandbox Code Playgroud)
它实际上使查询所需的时间增加了一倍。我用非聚集索引得到了同样的效果。
我尝试按如下方式重新编写它,但没有任何效果。
WITH r AS (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupId)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
)
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN r
ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
Run Code Online (Sandbox Code Playgroud)
接下来我尝试使用这样的窗口函数。
UPDATE [#TempTable]
SET Received = COUNT(DISTINCT (CASE WHEN Passed=1 THEN GroupId ELSE NULL END))
OVER (PARTITION BY AgentId, RuleId)
FROM [#TempTable]
Run Code Online (Sandbox Code Playgroud)
此时我开始收到错误
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near 'distinct'.
Run Code Online (Sandbox Code Playgroud)
所以我有两个问题。首先你不能用 OVER 子句做一个 COUNT DISTINCT 还是我写错了?其次,有人可以提出我还没有尝试过的改进吗?仅供参考,这是一个 SQL Server 2008 R2 Enterprise 实例。
编辑:这是原始执行计划的链接。我还应该注意,我的大问题是这个查询运行了 30-50 次。
https://onedrive.live.com/redir?resid=4C359AF42063BD98%21772
EDIT2:这是该语句在评论中要求的完整循环。我正在与定期处理此问题的人员核对循环的目的。
DECLARE @Counting INT
SELECT @Counting = 1
-- BEGIN: Cascading Rule check --
WHILE @Counting <= 30
BEGIN
UPDATE w1
SET Passed = 1
FROM [#TempTable] w1,
[#TempTable] w3
WHERE w3.AgentID = w1.AgentID AND
w3.RuleID = w1.CascadeRuleID AND
w3.RulePassed = 1 AND
w1.Passed = 0 AND
w1.NotFlag = 0
UPDATE w1
SET Passed = 1
FROM [#TempTable] w1,
[#TempTable] w3
WHERE w3.AgentID = w1.AgentID AND
w3.RuleID = w1.CascadeRuleID AND
w3.RulePassed = 0 AND
w1.Passed = 0 AND
w1.NotFlag = 1
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupID)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
) r ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
UPDATE [#TempTable]
SET RulePassed = 1
WHERE TotalNeeded = Received
SELECT @Counting = @Counting + 1
END
Run Code Online (Sandbox Code Playgroud)
Pau*_*ite 32
SQL Server 目前不支持这种构造。它可以(并且在我看来应该)在未来版本中实现。
应用报告此缺陷的反馈项中列出的解决方法之一,您的查询可以重写为:
WITH UpdateSet AS
(
SELECT
AgentID,
RuleID,
Received,
Calc = SUM(CASE WHEN rn = 1 THEN 1 ELSE 0 END) OVER (
PARTITION BY AgentID, RuleID)
FROM
(
SELECT
AgentID,
RuleID,
Received,
rn = ROW_NUMBER() OVER (
PARTITION BY AgentID, RuleID, GroupID
ORDER BY GroupID)
FROM #TempTable
WHERE Passed = 1
) AS X
)
UPDATE UpdateSet
SET Received = Calc;
Run Code Online (Sandbox Code Playgroud)
得到的执行计划是:

这具有避免用于万圣节保护的 Eager Table Spool 的优点(由于自联接),但它引入了一种排序(用于窗口)和一个通常效率低下的 Lazy Table Spool 构造来计算并将SUM OVER (PARTITION BY)结果应用于所有行在窗户里。它在实践中的表现是一项只有您才能进行的练习。
整体方法是一种难以很好地执行的方法。将更新(尤其是基于自联接的更新)以递归方式应用于大型结构可能有利于调试,但会导致性能下降。重复的大扫描、内存溢出和万圣节问题只是其中的一些问题。索引和(更多)临时表可以提供帮助,但需要非常仔细的分析,特别是如果索引被进程中的其他语句更新(维护索引会影响查询计划选择并添加 I/O)。
最终,解决根本问题将使咨询工作变得有趣,但这对本网站来说太过分了。不过,我希望这个答案能解决表面问题。
原始查询的替代解释(导致更新更多行):
WITH UpdateSet AS
(
SELECT
AgentID,
RuleID,
Received,
Calc = SUM(CASE WHEN Passed = 1 AND rn = 1 THEN 1 ELSE 0 END) OVER (
PARTITION BY AgentID, RuleID)
FROM
(
SELECT
AgentID,
RuleID,
Received,
Passed,
rn = ROW_NUMBER() OVER (
PARTITION BY AgentID, RuleID, Passed, GroupID
ORDER BY GroupID)
FROM #TempTable
) AS X
)
UPDATE UpdateSet
SET Received = Calc
WHERE Calc > 0;
Run Code Online (Sandbox Code Playgroud)

注意:消除排序(例如通过提供索引)可能会重新引入对 Eager Spool 或其他东西的需求,以提供必要的万圣节保护。Sort 是一个阻塞运算符,因此它提供了完整的相分离。
死灵法术:
使用 DENSE_RANK 模拟不同分区的计数相对简单:
;WITH baseTable AS
(
SELECT 'RM1' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM1' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR3' AS ADR
UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR
UNION ALL SELECT 'RM3' AS RM, 'ADR2' AS ADR
)
,CTE AS
(
SELECT RM, ADR, DENSE_RANK() OVER(PARTITION BY RM ORDER BY ADR) AS dr
FROM baseTable
-- Caveat: If there are NULL-values, add "WHERE ADR IS NOT NULL" here
)
SELECT
RM
,ADR
,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY ADR) AS cnt1
,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM) AS cnt2
-- Geht nicht / Doesn't work
--,COUNT(DISTINCT CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY CTE.ADR) AS cntDist
,MAX(CTE.dr) OVER (PARTITION BY CTE.RM ORDER BY CTE.RM) AS cntDistEmu
FROM CTE
Run Code Online (Sandbox Code Playgroud)
编辑:
警告:如果有空值,显然你需要添加 WHERE ADR IS NOT NULL 。