Bra*_*don 7 sql-server sql-server-2008-r2
我有一个存储过程 (MS SQL Server 2008 R2),需要查看 10 点/销售额是否落在 30 天滚动窗口内。到目前为止,我有一个有效的解决方案,但它不是一个“正确”的解决方案,因为它不是基于设置的。我使用 while 循环来处理正在发生的事情。查看代理的点数,我取第一个点的日期,看看是否还有 9 个在 30 天的窗口内,如果是,我将它们标记为已处理和已使用,然后继续下一个。我认为必须有一个基于集合的“更好”的解决方案。
为了看看我有什么,我设置了一个 SQL Fiddle。最后显示我们已经处理了3个代理,有4个奖励要给。有没有更好的方法来做到这一点而不使用更基于集合的 while 循环?
我试图组合出一种适用于 2008 年并且更加基于集合的方法。这是我想出的。
它最终确实比我希望的更复杂,但这可能是一种有趣的方法,可以让您在更大的数据集上对当前的方法进行基准测试。就其价值而言,对于我的机器上提供的数据集,该脚本的运行时间约为 15 毫秒(原始脚本的运行时间为 75 毫秒)。
正如其他人提到的,如果您能够在 2012 年以上使用窗口函数,或者可能在 2014 年使用本机编译的过程,可能还有其他更好的方法。但是有时考虑如何在没有更新功能的情况下做事会很有趣!
http://sqlfiddle.com/#!3/ad2be/7
-- Assign each point a sequential rank within each agent's history
SELECT p.internalID, ROW_NUMBER() OVER (PARTITION BY internalID ORDER BY date) AS recordRank, date
INTO #orderedPoints
FROM points p
-- Sort the data for efficient lookup of potential incentives
ALTER TABLE #orderedPoints
ADD UNIQUE CLUSTERED (internalId, recordRank)
-- Identify a potential incentive for any point that has 9+ points in the following 30 days
SELECT s.internalId, s.recordRank, ROW_NUMBER() OVER (PARTITION BY s.internalId ORDER BY s.recordRank) AS potentialIncentiveRank
INTO #potentialIncentives
FROM #orderedPoints s
JOIN #orderedPoints e
ON e.internalId = s.internalId
AND e.recordRank = s.recordRank + 9
AND e.date < DATEADD(dd, 30, s.date)
-- Sort the data to allow for efficient retrieval of subsequent incentives
ALTER TABLE #potentialIncentives
ADD UNIQUE CLUSTERED (internalId, recordRank)
-- A table to hold the incentives achieved
CREATE TABLE #incentives (internalId INT NOT NULL, recordRank INT NOT NULL)
-- A couple transient tables to hold the current "fringe" of incentives that were just inserted
CREATE TABLE #newlyProcessedIncentives (internalId INT NOT NULL, recordRank INT NOT NULL)
CREATE TABLE #newlyProcessedIncentives_forFromClause (internalId INT NOT NULL, recordRank INT NOT NULL)
-- Identify the first incentive for each agent
-- Note that TOP clauses and aggregate functions are not allowed in the recursive portion of a CTE
-- If that were allowed, this could serve as the anchor of a recursive CTE and the loop below would be the recursive portion
INSERT INTO #incentives (internalId, recordRank)
OUTPUT inserted.internalId, inserted.recordRank INTO #newlyProcessedIncentives (internalId, recordRank)
SELECT internalId, recordRank
FROM #potentialIncentives
WHERE potentialIncentiveRank = 1
-- Identify the next incentive for each agent, stopping when no further incentives are identified
WHILE EXISTS (SELECT TOP 1 * FROM #newlyProcessedIncentives)
BEGIN
-- Transfer the most recently identified incentives, so that we can truncate the table to capture the next iteration of incentives
TRUNCATE TABLE #newlyProcessedIncentives_forFromClause
INSERT INTO #newlyProcessedIncentives_forFromClause (internalId, recordRank) SELECT internalId, recordRank FROM #newlyProcessedIncentives
TRUNCATE TABLE #newlyProcessedIncentives
-- Identify the next incentive for each agent
INSERT INTO #incentives (internalId, recordRank)
OUTPUT inserted.internalId, inserted.recordRank INTO #newlyProcessedIncentives (internalId, recordRank)
SELECT nextIncentive.internalId, nextIncentive.recordRank
FROM #newlyProcessedIncentives_forFromClause f
CROSS APPLY (
SELECT TOP 1 p.*
FROM #potentialIncentives p
WHERE p.internalId = f.internalId
AND p.recordRank >= f.recordRank + 10 -- A new incentive can only start after all 10 points from the previous incentive
ORDER BY p.recordRank
) nextIncentive
END
-- Present the final results in the same format as the original implementation
SELECT a.internalId, a.points, i.incentives
FROM ( -- Tabulate points
SELECT internalId, MAX(recordRank) AS points
FROM #orderedPoints
GROUP BY internalId
) a
JOIN ( -- Tabulate incentives achieved
SELECT internalId, COUNT(*) AS incentives
FROM #incentives
GROUP BY internalId
) i
ON i.internalId = a.internalId
Run Code Online (Sandbox Code Playgroud)