Tôi đã cố gắng kết hợp một cách tiếp cận sẽ hoạt động vào năm 2008 và dựa trên cơ sở nhiều hơn. Đây là những gì tôi đã đưa ra.
Nó cuối cùng đã phức tạp hơn tôi mong đợi, nhưng nó có thể là một cách tiếp cận thú vị để bạn so sánh với cách tiếp cận hiện tại của bạn trên các tập dữ liệu lớn hơn. Để biết giá trị của nó, tập lệnh này chạy trong khoảng 15ms cho tập dữ liệu được cung cấp trên máy của tôi (so với 75ms cho tập lệnh gốc).
Như những người khác đã đề cập, có thể có những cách tiếp cận khác tốt hơn nếu bạn có thể sử dụng các chức năng của cửa sổ vào năm 2012+ hoặc có thể là một quy trình được biên dịch nguyên bản vào năm 2014. Nhưng thật thú vị khi nghĩ về cách làm mọi thứ mà không có các tính năng mới hơn đôi khi!
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