优化具有 While 循环和交叉应用的 T-SQL 查询
Posted
技术标签:
【中文标题】优化具有 While 循环和交叉应用的 T-SQL 查询【英文标题】:Optimizing a T-SQL query featuring While loop and Cross Apply 【发布时间】:2020-01-22 05:00:22 【问题描述】:寻求优化为计算滚动 12M 收入而编写的 T-SQL 查询。主要问题是我需要跨 6 个维度计算它,从而产生 45,000 多种组合,每个组合都需要自己的滚动收入。每个组合大约需要 5 分钟来计算。由于业务的性质,组合会随着时间的推移而增长。
所有操作都需要作为子查询完成。
DECLARE @NUMBER INT
DECLARE @ROWCOUNT INT = 0
DECLARE @REFERENCE VARCHAR(50)
SET @NUMBER =
(SELECT MAX(r.Row)
FROM (
SELECT
a.GroupKey
,a.LevelKey
,a.StateKey
,a.ProductKey
,a.OptionKey
,a.LocKey
,ROW_NUMBER() OVER(ORDER BY a.GroupKey ASC) AS Row
FROM Members AS a
GROUP BY
a.GroupKey
,a.LevelKey
,a.StateKey
,a.ProductKey
,a.OptionKey
,a.LocKey ) r)
BEGIN
DECLARE @OUTPUT TABLE (
RefKey VARCHAR(50)
,DateID INT
,GroupKey INT
,LevelKey INT
,StateKey INT
,ProductKey INT
,OptionKey INT
,LocKey INT
,Revenue DECIMAL(28,9)
,Rolling12Months DECIMAL(28,9)
)
WHILE @ROWCOUNT < @NUMBER
BEGIN
SET @REFERENCE =
(SELECT r.RefKey
FROM (
SELECT
r.Refkey
,r.Row
FROM (
SELECT
CONCAT( a.GroupKey, '-',a.LevelKey, '-',a.StateKey, '-',a.ProductKey, '-',a.OptionKey, '-',a.LocKey) AS RefKey
,ROW_NUMBER() OVER(ORDER BY a.GroupKey ASC) AS Row
FROM Members AS a
GROUP BY
a.GroupKey
,a.LevelKey
,a.StateKey
,a.ProductKey
,a.OptionKey
,a.LocKey ) r) r WHERE r.Row = @ROWCOUNT + 1 )
INSERT INTO @OUTPUT
SELECT
a.RefKey
,a.DateID
,a.GroupKey
,a.LevelKey
,a.StateKey
,a.ProductKey
,a.OptionKey
,a.LocKey
,a.Revenue
,Rolling12Months=a.Revenue + b.RM
FROM (
SELECT
CONVERT(datetime,STR(CI.DateID)) AS Date
,CI.DateID
,CI.GroupKey
,CI.LevelKey
,CI.StateKey
,CI.ProductKey
,CI.OptionKey
,CI.LocKey
,CONCAT( CI.GroupKey, '-',CI.LevelKey, '-',CI.StateKey, '-',CI.ProductKey, '-',CI.OptionKey, '-',CI.LocKey) AS RefKey
,SUM(CI.Revenue) AS Revenue
FROM Members AS CI
WHERE CONCAT( CI.GroupKey, '-',CI.LevelKey, '-',CI.StateKey, '-',CI.ProductKey, '-',CI.OptionKey, '-',CI.LocKey) = @REFERENCE
GROUP BY CI.DateID,CI.StateKey,CI.GroupKey ,CI.LevelKey,CI.StateKey,CI.ProductKey,CI.OptionKey ,CI.LocKey
) a
CROSS APPLY
(
SELECT RM=SUM(b.Revenue)
FROM
(
SELECT TOP 11 b.Date, b.Revenue
FROM (
SELECT
CONVERT(datetime,STR(CI.DateID)) AS Date
,CI.DateID
,CI.GroupKey
,CI.LevelKey
,CI.StateKey
,CI.ProductKey
,CI.OptionKey
,CI.LocKey
,CONCAT( CI.GroupKey, '-',CI.LevelKey, '-',CI.StateKey, '-',CI.ProductKey, '-',CI.OptionKey, '-',CI.LocKey) AS RefKey
,SUM(CI.Revenue) AS Revenue
FROM Members AS CI
WHERE CONCAT( CI.GroupKey, '-',CI.LevelKey, '-',CI.StateKey, '-',CI.ProductKey, '-',CI.OptionKey, '-',CI.LocKey) = @REFERENCE
GROUP BY CI.DateID,CI.StateKey,CI.GroupKey ,CI.LevelKey,CI.StateKey,CI.ProductKey,CI.OptionKey ,CI.LocKey
) b
WHERE b.Date < a.Date AND b.RefKey = a.RefKey
ORDER BY b.Date DESC
) b
) b
SET @ROWCOUNT = @ROWCOUNT + 1
END
SELECT * FROM @OUTPUT
END
【问题讨论】:
您应该做的第一件事可能是摆脱循环。 SQL 不能很好地处理循环,如果可能的话,你应该找到一个基于集合的方法来替换它。我没有阅读您发布的代码,但这是 SQL 中的一条黄金法则。 谢谢佐哈尔。我不确定是否有任何不使用循环来对属性进行聚类的方法。 【参考方案1】:我对你的代码有几点看法
尝试使用临时表而不是表变量 - 表变量可能存在多行性能问题。
通过连接很多列来动态生成一个键看起来很简洁,但是当你这样做时你会杀死任何索引或统计信息的使用。如果需要,加入单独的列,而不是在它们上使用函数。
您将日期列转换为日期时间,然后加入它 - 您应该再次使用未更改的列进行比较。
你没有解释你的日期列 - 你计算你的滚动 12 个月只是作为前 11 个最后日期小于当前日期,加上当前日期
,Rolling12Months=a.Revenue + b.RM
和
SELECT RM=SUM(b.Revenue)
FROM
(
SELECT TOP 11 b.Date, b.Revenue
您是否考虑过每个月可能有多个日期?在这种情况下,总和中您将获得少于 12 个月的数据。或者 ii,对于您按列分组的某种组合,有几个月没有数据,在这种情况下,您的总和将是更多个月!
但是,如果我们只需要按列对每个组合的最后 12 个日期求和,那么我会这样做:
/* When using many rows a temp table often performs better than a table variable */
DROP TABLE IF EXISTS #OUTPUT
CREATE TABLE #OUTPUT (
RefKey VARCHAR(50)
,DateID INT
,GroupKey INT
,LevelKey INT
,StateKey INT
,ProductKey INT
,OptionKey INT
,LocKey INT
,Revenue DECIMAL(28,9)
,Rolling12Months DECIMAL(28,9)
)
/* I am doing a simple select and using the analytical sum, which can be set up with a window */
SELECT
CONCAT(CI.GroupKey, '-', CI.LevelKey, '-', CI.StateKey, '-', CI.ProductKey, '-', CI.OptionKey, '-', CI.LocKey) RefKey
,CI.DateID
,CI.GroupKey
,CI.LevelKey
,CI.StateKey
,CI.ProductKey
,CI.OptionKey
,CI.LocKey
,CI.Revenue
,SUM(CI.Revenue) OVER (PARTITION BY CI.GroupKey, CI.LevelKey, CI.StateKey
, CI.ProductKey, CI.OptionKey, CI.LocKey ORDER BY CI.DateID
ROWS BETWEEN 11 PRECEDING AND CURRENT ROW) Rolling12Months
FROM Members AS CI
GROUP BY
CI.DateID
,CI.GroupKey
,CI.LevelKey
,CI.StateKey
,CI.ProductKey
,CI.OptionKey
,CI.LocKey
SELECT
*
FROM #OUTPUT
这应该会给您相同的结果,但是我不会按原样使用它,因为它只返回最后 11 个日期的总和加上每个列组合的当前日期!但如果它适用于您的原始代码,这应该会复制您的结果
【讨论】:
以上是关于优化具有 While 循环和交叉应用的 T-SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章