Чи можете ви використовувати COUNT DISTINCT із пунктом OVER?


25

Я намагаюся покращити ефективність наступного запиту:

        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                            

Наразі з моїми тестовими даними це займає близько хвилини. У мене обмежений обсяг вводу змін у всій збереженій процедурі, де знаходиться цей запит, але я, ймовірно, можу їх змусити змінити цей один запит. Або додати індекс. Я спробував додати наступний індекс:

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

І це фактично вдвічі збільшило кількість часу, яке потребує запит. Такий же ефект я отримую з індексом НЕ КЛАСТИРОВАНО.

Я намагався переписати його так, як це не було ефекту.

        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                            

Далі я спробував використати функцію вікон, як це.

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

У цей момент я почав отримувати помилку

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

Тож у мене є два питання. По-перше, ви не можете зробити ПІДГОТОВКУ ПІДГОТОВКУ за допомогою пункту OVER або я просто написав це неправильно? А по-друге, чи може хтось запропонувати поліпшення, якого я ще не пробував? FYI - це екземпляр Enterprise SQL Server 2008 R2 Enterprise.

EDIT: Ось посилання на початковий план виконання. Я також повинен зазначити, що моя велика проблема полягає в тому, що цей запит виконується 30-50 разів.

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

EDIT2: Ось повний цикл, про який йдеться у заяві, як вимагається в коментарях. Я перевіряю з людиною, яка працює з цим на регулярній основі, щодо цілі циклу.

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

Відповіді:


28

Ця конструкція наразі не підтримується в SQL Server. Це могло (і, на мою думку) може бути впроваджене в майбутньому варіанті.

Застосувавши одне із способів вирішення, вказаних у пункті зворотного зв’язку, що повідомляє про цей недолік, ваш запит може бути переписаний як:

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;

Отриманий план виконання:

План

Це має перевагу у тому, щоб уникнути захоплюючої котушки столу для захисту від Хеллоуїна (завдяки самостійному приєднанню), але вона вводить своєрідне (для вікна) та часто неефективну конструкцію ледачого столового шпулі для обчислення та застосування SUM OVER (PARTITION BY)результату до всіх рядів у вікно. Те, як це виконується на практиці, є лише вправою, яку ви можете виконати.

Загальний підхід є складним, щоб зробити його добре. Застосування оновлень (особливо на основі самостійного приєднання) рекурсивно до великої структури може бути корисним для налагодження, але це рецепт низької продуктивності. Багаторазові сканування, розлиття пам’яті та проблеми Хеллоуїна - лише деякі з питань. Індексація та (більше) тимчасові таблиці можуть допомогти, але дуже ретельний аналіз потрібен, особливо якщо індекс оновлюється іншими операторами в процесі (підтримка індексів впливає на вибір плану запитів та додає введення-виведення).

Зрештою, вирішення основної проблеми означало б цікаву консультаційну роботу, але це занадто багато для цього сайту. Я сподіваюся, що ця відповідь стосується поверхневого питання.


Альтернативна інтерпретація оригінального запиту (призводить до оновлення більшої кількості рядків):

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;

План 2

Примітка: усунення роду (наприклад, шляхом надання індексу) може знову ввести необхідність в Eager Spool або щось інше, щоб забезпечити необхідний захист на Хеллоуїн. Сортування є оператором блокування, тому він забезпечує повне розділення фаз.


6

Некромантування:

Емуляція рахунку, що відрізняється від розділу за допомогою 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
Семантика цього не така, як countякщо стовпчик є нульовим. Якщо вона містить будь-які нулі, вам потрібно відняти 1.
Мартін Сміт

@Martin Smith: Гарний улов. очевидно, що вам потрібно додати, де ADR НЕ нульовий, якщо є нульові значення.
Квандарі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.