Usando DISTINCT en la función de ventana con OVER

18

Estoy tratando de migrar una consulta de Oracle a SQL Server 2014.

Aquí está mi consulta que funciona muy bien en Oracle:

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

Aquí está el error que recibí después de intentar ejecutar esta consulta en SQL Server 2014.

Use of DISTINCT is not allowed with the OVER clause

Alguien sabe cuál es el problema? ¿Es posible este tipo de consulta en SQL Server? Por favor avise.

Omri
fuente
¿Realmente necesitas una fila en el resultado por cada fila MyTable? ¿O son suficientes filas distintas? ¿Y no necesita considerar el error de división por cero si no hay filas MyTable?
Erwin Brandstetter

Respuestas:

12

Alguien sabe cuál es el problema? ¿Es posible este tipo de consulta en SQL Server?

No, actualmente no está implementado. Consulte la siguiente solicitud de elemento de conexión.

Solicitud de mejora de la cláusula OVER: cláusula DISTINCT para funciones agregadas

Otra posible variante sería

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) 

el elenco NUMERICestá allí para evitar la división de enteros. El motivo de la cláusula join se explica aquí. .

Se puede reemplazar con ON M.B = T.B OR (M.B IS NULL AND T.B IS NULL)si se prefiere (o simplemente ON M.B = T.Bsi la Bcolumna no es anulable).

Martin Smith
fuente
14

Esto proporciona el recuento distinto (*) para A particionado por B:

dense_rank() over (partition by B order by A) 
+ dense_rank() over (partition by B order by A desc) 
- 1
Ben
fuente
3
Solución interesante Supongo que debería tener un descargo de responsabilidad de que funciona cuando solo Ano es anulable (ya que creo que también cuenta nulos).
ypercubeᵀᴹ
Debería ser abs(dense_rank - dense_rank) + 1, creo.
norcalli
7

Puede tomar el valor máximo de dense_rank()para obtener el recuento distintivo de A particionado por B.

Para ocuparse del caso en el que A puede tener valores nulos, puede usar first_valuepara determinar si un nulo está presente en la partición o no y luego restar 1 si es como lo sugirió Martin Smith en el comentario.

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
Mikael Eriksson
fuente
5

Intente hacer una subconsulta, agrupando por A, B e incluyendo el recuento. Luego, en su consulta externa, su recuento (distinto) se convierte en un recuento regular y su recuento (*) se convierte en una suma (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;
Rob Farley
fuente