Estoy tratando de escribir lo siguiente para obtener un total acumulado de NumUsers distintos, así:
NumUsers = COUNT(DISTINCT [UserAccountKey]) OVER (PARTITION BY [Mth])
Management Studio no parece muy feliz con esto. El error desaparece cuando elimino la DISTINCT
palabra clave, pero luego no será un recuento distinto.
DISTINCT
no parece ser posible dentro de las funciones de partición. ¿Cómo hago para encontrar el recuento distinto? ¿Utilizo un método más tradicional , como una subconsulta correlacionada?
Analizando esto un poco más, tal vez estas OVER
funciones funcionen de manera diferente a Oracle en la forma en que no se pueden usar SQL-Server
para calcular los totales acumulados.
Agregué un ejemplo en vivo aquí en SQLfiddle donde intento usar una función de partición para calcular un total acumulado .
COUNT
con enORDER BY
lugar dePARTITION BY
está mal definido en 2008. Me sorprende que te permita tenerlo. Según la documentación , no se le permite unaORDER BY
función agregada.Respuestas:
Hay una solución muy simple usando
dense_rank()
Esto le dará exactamente lo que estaba pidiendo: La cantidad de UserAccountKeys distintas dentro de cada mes.
fuente
dense_rank()
es que contará NULL, mientrasCOUNT(field) OVER
que no. No puedo emplearlo en mi solución debido a esto, pero sigo pensando que es bastante inteligente.NULL
valores en elUserAccountKey
, entonces es necesario agregar este término:-MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)
. La idea está tomada de la respuesta de LarsRönnbäck a continuación. Esencialmente, siUserAccountKey
tieneNULL
valores, debe restar extra1
del resultado, porqueDENSE_RANK
cuenta NULL.dense_rank
solución cuando la función de ventana tiene un marco. SQL Server no permite eldense_rank
uso con un marco de ventana: stackoverflow.com/questions/63527035/…Nigromante:
Es relativamente sencillo emular COUNT DISTINCT sobre PARTITION BY con MAX a través de 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:
Esto asume que los campos en cuestión son campos NO anulables.
Si hay una o más entradas NULL en los campos, debe restar 1.
fuente
Utilizo una solución similar a la de David anterior, pero con un giro adicional si algunas filas deben excluirse del recuento. Esto supone que [UserAccountKey] nunca es nulo.
-- 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
Aquí se puede encontrar un violín SQL con un ejemplo extendido.
fuente
[Include]
que está hablando en su respuesta) condense_rank()
trabajo cuandoUserAccountKey
sea posibleNULL
. Añadir este término a la fórmula:-MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)
.Creo que la única forma de hacer esto en SQL-Server 2008R2 es usar una subconsulta correlacionada o una aplicación externa:
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;
Esto se puede hacer en SQL-Server 2012 usando la sintaxis que sugirió:
SELECT datekey, SUM(Amount) OVER(ORDER BY DateKey) AS RunningTotal FROM document
Sin embargo, el uso de
DISTINCT
todavía no está permitido, por lo que si se requiere DISTINCT y / o si la actualización no es una opción, creo queOUTER APPLY
es su mejor opción.fuente