Bạn có thể sử dụng COUNT DISTINCT với mệnh đề QUÁ không?


25

Tôi đang cố gắng cải thiện hiệu suất của truy vấn sau:

        UPDATE  [#TempTable]
        SET     Received = r.Number
        FROM    [#TempTable] 
        INNER JOIN (SELECT  AgentID,
                            RuleID,
                            COUNT(DISTINCT (GroupId)) Number
                    FROM    [#TempTable]
                    WHERE   Passed = 1
                    GROUP BY AgentID,
                            RuleID
                   ) r ON r.RuleID = [#TempTable].RuleID AND
                          r.AgentID = [#TempTable].AgentID                            

Hiện tại với dữ liệu thử nghiệm của tôi mất khoảng một phút. Tôi có một số lượng đầu vào hạn chế vào các thay đổi trên tất cả các thủ tục được lưu trữ trong đó truy vấn này nằm trong đó nhưng tôi có thể có thể khiến chúng sửa đổi một truy vấn này. Hoặc thêm một chỉ mục. Tôi đã thử thêm chỉ số sau:

CREATE CLUSTERED INDEX ix_test ON #TempTable(AgentID, RuleId, GroupId, Passed)

Và nó thực sự tăng gấp đôi thời gian truy vấn. Tôi nhận được hiệu ứng tương tự với chỉ số NON-CLUSTERED.

Tôi đã thử viết lại nó như sau mà không có hiệu quả.

        WITH r AS (SELECT  AgentID,
                            RuleID,
                            COUNT(DISTINCT (GroupId)) Number
                    FROM    [#TempTable]
                    WHERE   Passed = 1
                    GROUP BY AgentID,
                            RuleID
            ) 
        UPDATE  [#TempTable]
        SET     Received = r.Number
        FROM    [#TempTable] 
        INNER JOIN r 
            ON r.RuleID = [#TempTable].RuleID AND
               r.AgentID = [#TempTable].AgentID                            

Tiếp theo tôi đã thử sử dụng một chức năng cửa sổ như thế này.

        UPDATE  [#TempTable]
        SET     Received = COUNT(DISTINCT (CASE WHEN Passed=1 THEN GroupId ELSE NULL END)) 
                    OVER (PARTITION BY AgentId, RuleId)
        FROM    [#TempTable] 

Lúc này tôi bắt đầu nhận lỗi

Msg 102, Level 15, State 1, Line 2
Incorrect syntax near 'distinct'.

Vì vậy, tôi có hai câu hỏi. Trước tiên, bạn không thể thực hiện một DISTINCT với mệnh đề QUÁ hay tôi chỉ viết sai? Và thứ hai có ai có thể đề xuất một cải tiến mà tôi chưa thử không? FYI đây là một phiên bản SQL Server 2008 R2 Enterprise.

EDIT: Đây là một liên kết đến kế hoạch thực hiện ban đầu. Tôi cũng cần lưu ý rằng vấn đề lớn của tôi là truy vấn này đang được chạy 30-50 lần.

https://iatedrive.live.com/redir?resid=4C359AF42063BD98%21772

EDIT2: Đây là vòng lặp đầy đủ mà câu lệnh được yêu cầu trong các bình luận. Tôi đang kiểm tra với người làm việc với điều này một cách thường xuyên về mục đích của vòng lặp.

DECLARE @Counting INT              
SELECT  @Counting = 1              

--  BEGIN:  Cascading Rule check --           
WHILE @Counting <= 30              
    BEGIN      

        UPDATE  w1
        SET     Passed = 1
        FROM    [#TempTable] w1,
                [#TempTable] w3
        WHERE   w3.AgentID = w1.AgentID AND
                w3.RuleID = w1.CascadeRuleID AND
                w3.RulePassed = 1 AND
                w1.Passed = 0 AND
                w1.NotFlag = 0      

        UPDATE  w1
        SET     Passed = 1
        FROM    [#TempTable] w1,
                [#TempTable] w3
        WHERE   w3.AgentID = w1.AgentID AND
                w3.RuleID = w1.CascadeRuleID AND
                w3.RulePassed = 0 AND
                w1.Passed = 0 AND
                w1.NotFlag = 1        

        UPDATE  [#TempTable]
        SET     Received = r.Number
        FROM    [#TempTable] 
        INNER JOIN (SELECT  AgentID,
                            RuleID,
                            COUNT(DISTINCT (GroupID)) Number
                    FROM    [#TempTable]
                    WHERE   Passed = 1
                    GROUP BY AgentID,
                            RuleID
                   ) r ON r.RuleID = [#TempTable].RuleID AND
                          r.AgentID = [#TempTable].AgentID                            

        UPDATE  [#TempTable]
        SET     RulePassed = 1
        WHERE   TotalNeeded = Received              

        SELECT  @Counting = @Counting + 1              
    END

Câu trả lời:


28

Cấu trúc này hiện không được hỗ trợ trong SQL Server. Nó có thể (và nên, theo ý kiến ​​của tôi) được thực hiện trong phiên bản tương lai.

Áp dụng một trong các cách giải quyết được liệt kê trong mục phản hồi báo cáo thiếu sót này, truy vấn của bạn có thể được viết lại thành:

WITH UpdateSet AS
(
    SELECT 
        AgentID, 
        RuleID, 
        Received, 
        Calc = SUM(CASE WHEN rn = 1 THEN 1 ELSE 0 END) OVER (
            PARTITION BY AgentID, RuleID) 
    FROM 
    (
        SELECT  
            AgentID,
            RuleID,
            Received,
            rn = ROW_NUMBER() OVER (
                PARTITION BY AgentID, RuleID, GroupID 
                ORDER BY GroupID)
        FROM    #TempTable
        WHERE   Passed = 1
    ) AS X
)
UPDATE UpdateSet
SET Received = Calc;

Kế hoạch thực hiện kết quả là:

Kế hoạch

Điều này có lợi thế là tránh được Bộ đệm bảng háo hức để bảo vệ Halloween (do tự tham gia), nhưng nó giới thiệu một loại (cho cửa sổ) và cấu trúc Bộ đệm bảng lười biếng thường không hiệu quả để tính toán và áp dụng SUM OVER (PARTITION BY)kết quả cho tất cả các hàng trong cửa sổ. Làm thế nào nó thực hiện trong thực tế là một bài tập chỉ bạn có thể thực hiện.

Cách tiếp cận tổng thể là một khó khăn để thực hiện tốt. Áp dụng các bản cập nhật (đặc biệt là các bản cập nhật dựa trên tự tham gia) theo cách đệ quy cho một cấu trúc lớn có thể tốt cho việc gỡ lỗi nhưng nó là một công thức cho hiệu suất kém. Lặp đi lặp lại quét lớn, tràn bộ nhớ và các vấn đề Halloween chỉ là một số vấn đề. Lập chỉ mục và (nhiều hơn) các bảng tạm thời có thể giúp ích, nhưng cần phân tích rất cẩn thận đặc biệt là nếu chỉ mục được cập nhật bởi các câu lệnh khác trong quy trình (duy trì các chỉ mục ảnh hưởng đến các lựa chọn kế hoạch truy vấn và thêm I / O).

Cuối cùng, giải quyết vấn đề cơ bản sẽ làm cho công việc tư vấn thú vị, nhưng nó là quá nhiều cho trang web này. Tôi hy vọng câu trả lời này giải quyết các câu hỏi bề mặt mặc dù.


Giải thích thay thế cho truy vấn ban đầu (kết quả là cập nhật nhiều hàng hơn):

WITH UpdateSet AS
(
    SELECT 
        AgentID, 
        RuleID, 
        Received, 
        Calc = SUM(CASE WHEN Passed = 1 AND rn = 1 THEN 1 ELSE 0 END) OVER (
            PARTITION BY AgentID, RuleID) 
    FROM 
    (
        SELECT  
            AgentID,
            RuleID,
            Received,
            Passed,
            rn = ROW_NUMBER() OVER (
                PARTITION BY AgentID, RuleID, Passed, GroupID
                ORDER BY GroupID)
        FROM    #TempTable
    ) AS X
)
UPDATE UpdateSet
SET Received = Calc
WHERE Calc > 0;

Kế hoạch 2

Lưu ý: loại bỏ sắp xếp (ví dụ: bằng cách cung cấp một chỉ mục) có thể giới thiệu lại nhu cầu về Spool Eager hoặc một cái gì đó khác để cung cấp Bảo vệ Halloween cần thiết. Sắp xếp là một toán tử chặn, vì vậy nó cung cấp tách pha đầy đủ.


6

Cần thiết:

Thật đơn giản để mô phỏng số đếm khác biệt trên phân vùng bằng DENSE_RANK:

;WITH baseTable AS
(
              SELECT 'RM1' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM1' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR3' AS ADR
    UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM3' AS RM, 'ADR2' AS ADR
)
,CTE AS
(
    SELECT RM, ADR, DENSE_RANK() OVER(PARTITION BY RM ORDER BY ADR) AS dr 
    FROM baseTable
)
SELECT
     RM
    ,ADR

    ,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY ADR) AS cnt1 
    ,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM) AS cnt2 
    -- Geht nicht / Doesn't work 
    --,COUNT(DISTINCT CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY CTE.ADR) AS cntDist
    ,MAX(CTE.dr) OVER (PARTITION BY CTE.RM ORDER BY CTE.RM) AS cntDistEmu 
FROM CTE

3
Các ngữ nghĩa của điều này không giống như countnếu cột là nullable. Nếu nó chứa bất kỳ giá trị nào bạn cần trừ 1.
Martin Smith

@Martin Smith: Bắt tốt đẹp. rõ ràng bạn cần thêm WHERE ADR KHÔNG phải là NULL nếu có giá trị null.
Quandary
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.