Utilizzo di DISTINCT nella funzione finestra con OVER


18

Sto cercando di migrare una query da Oracle a SQL Server 2014.

Ecco la mia query che funziona alla grande in Oracle:

select
count(distinct A) over (partition by B) / count(*) over() as A_B
from MyTable 

Ecco l'errore che ho ricevuto dopo aver tentato di eseguire questa query in SQL Server 2014.

Use of DISTINCT is not allowed with the OVER clause

Qualcuno sa qual è il problema? Questo tipo di query è possibile in SQL Server? Si prega di avvisare.


Hai davvero bisogno di una riga nel risultato per ogni riga in MyTable? O sono abbastanza righe distinte? E non c'è bisogno di prendere in considerazione la divisione per zero errore se non ci sono nessun righe MyTable?
Erwin Brandstetter,

Risposte:


12

Qualcuno sa qual è il problema? Questo tipo di query è possibile in SQL Server?

No, non è attualmente implementato. Vedere la seguente richiesta di elemento di connessione.

Richiesta di miglioramento della clausola OVER: clausola DISTINCT per funzioni aggregate

Un'altra possibile variante sarebbe

SELECT M.A,
       M.B,
       T.A_B
FROM   MyTable M
       JOIN (SELECT CAST(COUNT(DISTINCT A) AS NUMERIC(18,8)) / SUM(COUNT(*)) OVER() AS A_B,
                    B
             FROM   MyTable
             GROUP  BY B) T
         ON EXISTS (SELECT M.B INTERSECT SELECT T.B) 

il cast a NUMERICè lì per evitare la divisione di numeri interi. Il motivo della clausola di join è spiegato qui .

Può essere sostituito con ON M.B = T.B OR (M.B IS NULL AND T.B IS NULL)se preferito (o semplicemente ON M.B = T.Bse la Bcolonna non è nullable).


14

Questo dà il conteggio distinto (*) per A partizionato da B:

dense_rank() over (partition by B order by A) 
+ dense_rank() over (partition by B order by A desc) 
- 1

3
Soluzione interessante. Suppongo che dovrebbe avere una dichiarazione di non responsabilità che funziona solo quando Aè non annullabile (poiché penso che contenga anche i null).
ypercubeᵀᴹ

Dovrei essere abs(dense_rank - dense_rank) + 1io credo.
norcalli,

7

Puoi prendere il valore massimo di dense_rank()per ottenere il conteggio distinto di A partizionato per B.

Per occuparsi del caso in cui A può avere valori null, è possibile utilizzare first_valueper capire se è presente un null nella partizione o meno e quindi sottrarre 1 se è come suggerito da Martin Smith nel commento.

select (max(T.DenseRankA) over(partition by T.B) - 
          cast(iif(T.FirstA is null, 1, 0) as numeric(18, 8))) / T.TotalCount as A_B
from (
     select dense_rank() over(partition by T.B order by T.A) DenseRankA,
            first_value(T.A) over(partition by T.B order by T.A) as FirstA,
            count(*) over() as TotalCount,
            T.A,
            T.B
     from MyTable as T
     ) as T

5

Prova a eseguire una sottoquery, raggruppando per A, B e includendo il conteggio. Quindi nella tua query esterna, il tuo conteggio (distinto) diventa un conteggio regolare e il tuo conteggio (*) diventa una somma (cnt).

select
count(A) over (partition by B) * 1.0 / 
    sum(cnt) over() as A_B
from
(select A, B, count(*) as cnt
 from MyTable
 group by A, B) as partial;
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.