OVER 절과 함께 COUNT DISTINCT를 사용할 수 있습니까?


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                            

현재 테스트 데이터는 약 1 분이 걸립니다. 이 쿼리가있는 모든 저장 프로 시저에 대한 변경 사항에 제한적인 입력이 있지만이 쿼리를 수정하도록 할 수 있습니다. 또는 색인을 추가하십시오. 다음 색인을 추가하려고했습니다.

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

실제로 쿼리 시간이 두 배로 늘어났습니다. NON-CLUSTERED 인덱스와 동일한 효과를 얻습니다.

아무런 효과없이 다음과 같이 다시 작성해 보았습니다.

        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 절로 COUNT DISTINCT를 수행 할 수 없거나 잘못 쓴 것입니까? 두 번째로 아직 시도하지 않은 개선 사항을 제안 할 수 있습니까? 참고로 이것은 SQL Server 2008 R2 Enterprise 인스턴스입니다.

편집 : 다음은 원래 실행 계획에 대한 링크입니다. 또한 내 큰 문제는이 쿼리가 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)결과를 계산하고 모든 행에 적용하기 위해 종종 비효율적 인 지연 테이블 스풀 구성을 도입 합니다 창에서. 실제로 수행하는 방법은 수행 할 수있는 운동입니다.

전반적인 접근 방식은 잘 수행하기 어렵습니다. 대규모 구조에 업데이트 (특히 자체 조인 기반 업데이트)를 재귀 적으로 적용하면 디버깅에 좋지만 성능 저하의 레시피입니다. 반복되는 큰 스캔, 메모리 유출 및 할로윈 문제는 일부 문제입니다. 인덱싱 및 (더 많은) 임시 테이블이 도움이 될 수 있지만 프로세스의 다른 명령문으로 인덱스가 업데이트되는 경우 특히 신중한 분석이 필요합니다 (인덱스 유지는 쿼리 계획 선택에 영향을 미치고 I / O를 추가 함).

궁극적으로 근본적인 문제를 해결하면 흥미로운 컨설팅 작업이 이루어 지지만이 사이트에는 너무 많은 문제가됩니다. 이 답변이 표면 문제를 해결하기를 바랍니다.


원래 쿼리의 대체 해석 (더 많은 행을 업데이트 한 결과) :

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 스풀이나 필요한 할로윈 보호 기능을 제공하는 데 필요한 것이 다시 도입 될 수 있습니다. 정렬은 차단 연산자이므로 완전한 상 분리를 제공합니다.


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 IS NULL이 아닌 경우를 추가해야합니다.
Quandary
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.