Tìm kiếm một giải pháp dựa trên thiết lập cho thời gian 30 ngày


7

Tôi có một quy trình được lưu trữ (MS SQL Server 2008 R2) cần xem 10 điểm / doanh số có nằm trong cửa sổ 30 ngày không. Cho đến nay, tôi có một giải pháp hiệu quả, nhưng đó không phải là giải pháp "phù hợp" vì nó không được đặt. Tôi sử dụng các vòng lặp để xử lý những gì đang diễn ra. Nhìn vào điểm của một đại lý, tôi lấy ngày của điểm đầu tiên và xem liệu 9 điểm nữa có nằm trong cửa sổ 30 ngày không, nếu vậy, tôi đánh dấu chúng là đã được xử lý và sử dụng và chuyển sang điểm tiếp theo. Tôi nghĩ rằng phải có một giải pháp "tốt hơn" dựa trên thiết lập.

Để xem những gì tôi có, tôi thiết lập SQL Fiddle. Cuối cùng, chúng tôi đã xử lý 3 đại lý và có 4 ưu đãi được đưa ra. Có cách nào tốt hơn để làm điều này mà không cần sử dụng trong khi các vòng lặp dựa trên tập hợp hơn không?

Câu đố SQL


1
Khác với SQL Fiddle tôi đặt cùng nhau?
Brandon

Bất kỳ cơ hội nào bạn có thể chuyển sang SQL Server 2012? Cải tiến chức năng cửa sổ có lẽ sẽ làm cho giải pháp của bạn đơn giản hơn rất nhiều.
Aaron Bertrand

Tiếc là không có. :(
Brandon

3
Nếu không có ngữ nghĩa cửa sổ, cách tiếp cận không dựa trên tập hợp của bạn trong thực tế có thể là phương pháp tối ưu (hoặc đủ tốt). Tôi đã không điều tra vấn đề cụ thể này trong kịch bản của bạn nhưng đó là kết luận của tôi về việc chạy tổng số - tùy chọn được hỗ trợ tốt nhất , IMHO, cho <2012 thực sự là một con trỏ.
Aaron Bertrand

1
Tôi thường sử dụng thủ tục SQLCLR cho việc này. Nếu tôi có thời gian vào ngày mai tôi có thể đóng góp một triển khai như một câu trả lời, cho hậu thế nếu không có gì khác.
Paul White 9

Câu trả lời:


1

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.