Bisakah Anda menggunakan COUNT DISTINCT dengan klausa OVER?

25

Saya mencoba meningkatkan kinerja kueri berikut:

        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                            

Saat ini dengan data pengujian saya, dibutuhkan sekitar satu menit. Saya memiliki sejumlah input terbatas pada perubahan pada seluruh prosedur tersimpan di mana kueri ini berada, tetapi saya mungkin bisa meminta mereka untuk memodifikasi kueri yang satu ini. Atau tambahkan indeks. Saya mencoba menambahkan indeks berikut:

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

Dan itu sebenarnya menggandakan jumlah waktu yang dibutuhkan kueri. Saya mendapatkan efek yang sama dengan indeks NON-CLUSTERED.

Saya mencoba menulis ulang sebagai berikut tanpa efek.

        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                            

Selanjutnya saya mencoba menggunakan fungsi windowing seperti ini.

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

Pada titik ini saya mulai mendapatkan kesalahan

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

Jadi saya punya dua pertanyaan. Pertama, bisakah Anda tidak melakukan COUNT DISTINCT dengan klausa OVER atau apakah saya salah menuliskannya? Dan kedua, adakah yang bisa menyarankan perbaikan yang belum pernah saya coba? FYI ini adalah contoh SQL Server 2008 R2 Enterprise.

EDIT: Berikut ini tautan ke rencana eksekusi asli. Saya juga harus mencatat bahwa masalah besar saya adalah bahwa kueri ini dijalankan 30-50 kali.

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

EDIT2: Berikut ini adalah loop penuh pernyataan itu seperti yang diminta dalam komentar. Saya memeriksa dengan orang yang bekerja dengan ini secara teratur untuk tujuan loop.

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
Kenneth Fisher
sumber

Jawaban:

28

Konstruksi ini saat ini tidak didukung di SQL Server. Itu bisa (dan seharusnya, menurut saya) diimplementasikan dalam versi masa depan.

Menerapkan salah satu solusi yang tercantum dalam item umpan balik melaporkan kekurangan ini, permintaan Anda dapat ditulis ulang sebagai:

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;

Rencana eksekusi yang dihasilkan adalah:

Rencana

Ini memiliki keuntungan menghindari Eool Table Spool untuk Halloween Protection (karena self-join), tetapi memperkenalkan semacam (untuk jendela) dan konstruksi Spool Malas Lazy yang sering tidak efisien untuk menghitung dan menerapkan SUM OVER (PARTITION BY)hasilnya pada semua baris di jendela. Bagaimana kinerjanya dalam latihan adalah latihan yang hanya dapat Anda lakukan.

Pendekatan keseluruhan adalah pendekatan yang sulit untuk dilakukan dengan baik. Menerapkan pembaruan (terutama yang didasarkan pada self-gabung) secara rekursif ke struktur besar mungkin baik untuk debugging tetapi itu adalah resep untuk kinerja yang buruk. Pemindaian besar berulang, tumpahan memori, dan masalah Halloween hanyalah beberapa masalah. Pengindeksan dan (lebih banyak) tabel sementara dapat membantu, tetapi analisis yang sangat hati-hati diperlukan terutama jika indeks diperbarui oleh pernyataan lain dalam proses (mempertahankan indeks memengaruhi pilihan rencana kueri dan menambahkan I / O).

Pada akhirnya, menyelesaikan masalah mendasar akan membuat pekerjaan konsultasi yang menarik, tetapi terlalu banyak untuk situs ini. Saya harap jawaban ini menjawab pertanyaan permukaan.


Interpretasi alternatif dari kueri asli (menghasilkan pembaruan lebih banyak baris):

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;

Paket 2

Catatan: menghilangkan semacam itu (misalnya dengan memberikan indeks) mungkin memperkenalkan kembali kebutuhan untuk Eager Spool atau sesuatu yang lain untuk menyediakan Perlindungan Halloween yang diperlukan. Sort adalah operator pemblokiran, sehingga memberikan pemisahan fase penuh.

Paul White mengatakan GoFundMonica
sumber
6

Necromancing:

Relatif mudah untuk meniru jumlah yang berbeda dari partisi dengan dengan 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
Kebingungan
sumber
3
Semantiknya tidak sama dengan countjika kolom tersebut dapat dibatalkan. Jika mengandung nol, Anda harus mengurangi 1.
Martin Smith
@ Martin Smith: Tangkapan bagus. sebelumnya Anda perlu menambahkan WHERE ADR NOT NOT NULL jika ada nilai null.
Quandary