为滚动 30 天寻找基于集合的解决方案

Bra*_*don 7 sql-server sql-server-2008-r2

我有一个存储过程 (MS SQL Server 2008 R2),需要查看 10 点/销售额是否落在 30 天滚动窗口内。到目前为止,我有一个有效的解决方案,但它不是一个“正确”的解决方案,因为它不是基于设置的。我使用 while 循环来处理正在发生的事情。查看代理的点数,我取第一个点的日期,看看是否还有 9 个在 30 天的窗口内,如果是,我将它们标记为已处理和已使用,然后继续下一个。我认为必须有一个基于集合的“更好”的解决方案。

为了看看我有什么,我设置了一个 SQL Fiddle。最后显示我们已经处理了3个代理,有4个奖励要给。有没有更好的方法来做到这一点而不使用更基于集合的 while 循环?

SQL小提琴

Geo*_*son 1

我试图组合出一种适用于 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)