Funzione di partizione COUNT () OVER possibile utilizzando DISTINCT


90

Sto cercando di scrivere quanto segue per ottenere un totale parziale di NumUsers distinti, in questo modo:

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

Lo studio di gestione non sembra molto contento di questo. L'errore scompare quando rimuovo la DISTINCTparola chiave, ma non sarà un conteggio distinto.

DISTINCTnon sembra essere possibile all'interno delle funzioni di partizione. Come posso trovare il conteggio distinto? Uso un metodo più tradizionale come una sottoquery correlata?

Esaminando questo un po 'più in là, forse questi OVER funzioni funzionano in modo diverso da Oracle in quanto non possono essere utilizzate SQL-Serverper calcolare i totali correnti.

Ho aggiunto un esempio dal vivo qui su SQLfiddle in cui cerco di utilizzare una funzione di partizione per calcolare un totale parziale .


2
COUNTwith ORDER BYinvece di PARTITION BYè mal definito nel 2008. Sono sorpreso che ti permetta di averlo. Secondo la documentazione , non è consentito l'uso ORDER BYdi una funzione aggregata.
Damien_The_Unbeliever

sì - penso che mi sto confondendo con alcune funzionalità di Oracle; questi totali
parziali

Risposte:


180

C'è una soluzione molto semplice usando dense_rank()

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

Questo ti darà esattamente quello che stavi chiedendo: il numero di distinte UserAccountKeys in ogni mese.


23
Una cosa a cui prestare attenzione dense_rank()è che conterà NULL mentre COUNT(field) OVERnon lo fa. Non posso utilizzarlo nella mia soluzione per questo motivo, ma continuo a pensare che sia abbastanza intelligente.
bf2020

1
Ma sto cercando un totale parziale di chiavi account utente distinte nel corso dei mesi di ogni anno: non sei sicuro di come questo risponda?
whytheq

4
@ bf2020, se ci possono essere NULLvalori nella UserAccountKey, allora avete bisogno di aggiungere questo termine: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth). L'idea è presa dalla risposta di LarsRönnbäck di seguito. In sostanza, se UserAccountKeyha NULLvalori, è necessario sottrarre extra 1dal risultato, perché DENSE_RANKconta NULL.
Vladimir Baranov

Ecco una discussione sull'utilizzo di questa dense_ranksoluzione quando la funzione finestra ha una cornice. SQL Server non consente l' dense_rankutilizzo con una cornice della finestra: stackoverflow.com/questions/63527035/…
K4M

6

Negromante:

È relativamente semplice emulare un COUNT DISTINCT su PARTITION BY con MAX tramite 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

Nota:
questo presuppone che i campi in questione siano campi NON annullabili.
Se sono presenti una o più voci NULL nei campi, è necessario sottrarre 1.


5

Uso una soluzione simile a quella di David sopra, ma con una svolta aggiuntiva se alcune righe devono essere escluse dal conteggio. Ciò presuppone che [UserAccountKey] non sia mai nullo.

-- 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

Un SQL Fiddle con un esempio esteso può essere trovato qui.


1
La tua idea può essere utilizzata per creare la formula originale (senza complessità di [Include]cui stai parlando nella tua risposta) con il dense_rank()lavoro quando UserAccountKeypuò essere NULL. Aggiungere questo termine per la formula: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth).
Vladimir Baranov

5

Penso che l'unico modo per farlo in SQL-Server 2008R2 sia utilizzare una sottoquery correlata o un'applicazione esterna:

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;

Questo può essere fatto in SQL-Server 2012 utilizzando la sintassi che hai suggerito:

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

Tuttavia, l'uso di non DISTINCTè ancora consentito, quindi se DISTINCT è richiesto e / o se l'aggiornamento non è un'opzione, penso che OUTER APPLYsia la tua migliore opzione


cool grazie. Ho trovato questa risposta SO che presenta l'opzione OUTER APPLY che cercherò. Hai visto l'approccio di aggiornamento in loop in quella risposta ... è abbastanza lontano e apparentemente veloce. La vita sarà più facile nel 2012: è una copia originale di Oracle?
whytheq
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.