Fungsi Partisi COUNT () LEBIH BANYAK dimungkinkan menggunakan DISTINCT


90

Saya mencoba untuk menulis yang berikut ini untuk mendapatkan total NumUsers yang berbeda, seperti:

NumUsers = COUNT(DISTINCT [UserAccountKey]) OVER (PARTITION BY [Mth])

Studio manajemen sepertinya tidak terlalu senang dengan ini. Kesalahan menghilang saat saya menghapus DISTINCTkata kunci, tetapi kemudian tidak akan menjadi hitungan yang berbeda.

DISTINCTtampaknya tidak dimungkinkan dalam fungsi partisi. Bagaimana cara saya mencari hitungan berbeda? Apakah saya menggunakan metode yang lebih tradisional seperti subkueri berkorelasi?

Melihat lebih jauh, mungkin OVERfungsi - fungsi ini bekerja secara berbeda dengan Oracle sehingga tidak dapat digunakan SQL-Serveruntuk menghitung total yang berjalan.

Saya telah menambahkan contoh langsung di sini di SQLfiddle di mana saya mencoba menggunakan fungsi partisi untuk menghitung total yang berjalan.


2
COUNTdengan ORDER BYbukannya PARTITION BYtidak jelas pada tahun 2008. Saya terkejut itu membiarkan Anda memilikinya sama sekali. Berdasarkan dokumentasi , Anda tidak diizinkan ORDER BYuntuk fungsi agregat.
Damien_The_Unbeliever

ya - pikir saya bingung dengan beberapa fungsi oracle; ini total berjalan dan hitungan berjalan akan sedikit lebih terlibat
whytheq

Jawaban:


180

Ada solusi yang sangat sederhana dengan menggunakan dense_rank()

dense_rank() over (partition by [Mth] order by [UserAccountKey]) 
+ dense_rank() over (partition by [Mth] order by [UserAccountKey] desc) 
- 1

Ini akan memberi Anda apa yang Anda minta: Jumlah UserAccountKey yang berbeda dalam setiap bulan.


23
Satu hal yang harus diperhatikan dense_rank()adalah bahwa ia akan menghitung NULL sedangkan COUNT(field) OVERtidak. Saya tidak dapat menerapkannya dalam solusi saya karena ini tetapi saya masih menganggapnya cukup pintar.
bf2020

1
Tapi saya mencari total penggunaakunci akun yang berbeda selama bulan-bulan setiap tahun: tidak yakin bagaimana ini menjawabnya?
whytheq

4
@ bf2020, jika bisa ada NULLnilai-nilai dalam UserAccountKey, maka Anda perlu menambahkan istilah ini: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth). Ide diambil dari jawaban LarsRönnbäck di bawah ini. Pada dasarnya, jika UserAccountKeymemiliki NULLnilai, Anda perlu mengurangi ekstra 1dari hasilnya, karena DENSE_RANKmenghitung NULL.
Vladimir Baranov

Di sini diskusi tentang menggunakan dense_ranksolusi ini ketika fungsi jendela memiliki bingkai. SQL Server tidak mengizinkan dense_rankpenggunaan dengan bingkai jendela: stackoverflow.com/questions/63527035/…
K4M

6

Necromancing:

Sangat mudah untuk meniru COUNT DISTINCT selama PARTITION BY dengan MAX melalui 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 
    -- Not supported
    --,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

Catatan:
Ini mengasumsikan bidang yang dimaksud adalah bidang NON-nullable.
Jika ada satu atau lebih entri NULL di bidang, Anda perlu mengurangi 1.


5

Saya menggunakan solusi yang mirip dengan David di atas, tetapi dengan tambahan twist jika beberapa baris harus dikecualikan dari hitungan. Ini mengasumsikan bahwa [UserAccountKey] tidak pernah nol.

-- subtract an extra 1 if null was ranked within the partition,
-- which only happens if there were rows where [Include] <> 'Y'
dense_rank() over (
  partition by [Mth] 
  order by case when [Include] = 'Y' then [UserAccountKey] else null end asc
) 
+ dense_rank() over (
  partition by [Mth] 
  order by case when [Include] = 'Y' then [UserAccountKey] else null end desc
)
- max(case when [Include] = 'Y' then 0 else 1 end) over (partition by [Mth])
- 1

Sebuah SQL Fiddle dengan contoh tambahan dapat ditemukan di sini.


1
Ide Anda dapat digunakan untuk membuat rumus asli (tanpa kerumitan [Include]yang Anda bicarakan dalam jawaban Anda) dengan dense_rank()bekerja jika UserAccountKeybisa NULL. Menambahkan istilah ini ke rumus: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth).
Vladimir Baranov

5

Saya pikir satu-satunya cara untuk melakukan ini di SQL-Server 2008R2 adalah dengan menggunakan subkueri berkorelasi, atau penerapan luar:

SELECT  datekey,
        COALESCE(RunningTotal, 0) AS RunningTotal,
        COALESCE(RunningCount, 0) AS RunningCount,
        COALESCE(RunningDistinctCount, 0) AS RunningDistinctCount
FROM    document
        OUTER APPLY
        (   SELECT  SUM(Amount) AS RunningTotal,
                    COUNT(1) AS RunningCount,
                    COUNT(DISTINCT d2.dateKey) AS RunningDistinctCount
            FROM    Document d2
            WHERE   d2.DateKey <= document.DateKey
        ) rt;

Ini dapat dilakukan di SQL-Server 2012 menggunakan sintaks yang Anda sarankan:

SELECT  datekey,
        SUM(Amount) OVER(ORDER BY DateKey) AS RunningTotal
FROM    document

Namun, penggunaan DISTINCTmasih tidak diperbolehkan, jadi jika DISTINCT diperlukan dan / atau jika peningkatan bukanlah suatu pilihan maka saya pikir OUTER APPLYadalah pilihan terbaik Anda


keren terima kasih. Saya menemukan jawaban SO ini yang menampilkan opsi OUTER APPLY yang akan saya coba. Pernahkah Anda melihat pendekatan UPDATE perulangan dalam jawaban itu ... itu cukup jauh & tampaknya cepat. Hidup akan lebih mudah di tahun 2012 - apakah itu salinan asli Oracle?
whytheq
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.