2012-06-26 35 views
42

Estoy intentando escribir la siguiente con el fin de obtener un total acumulado de numUsers distintas, así:partición función count() OVER posible usando DISTINCT

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

Management Studio no parece muy feliz por esta. El error desaparece cuando elimino la palabra clave DISTINCT, pero luego no será un conteo distinto.

DISTINCT no parece 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?

Al analizar esto un poco más, tal vez estas funciones OVER funcionen de forma diferente a Oracle en la forma en que no se pueden usar en SQL-Server para calcular los totales acumulados.

He agregado un ejemplo en vivo aquí en SQLfiddle donde intento utilizar una función de partición para calcular un total acumulado.

+2

'' COUNT' con ORDEN BY' en lugar de 'PARTITION BY' está mal definido en 2008. Me sorprende que te permita tenerlo. Según la [documentación] (http://msdn.microsoft.com/en-us/library/ms189461 (v = sql.105) .aspx), no tiene permitido un 'ORDER BY' para una función agregada. –

+0

sí, creo que me confunden algunas funciones de Oracle; estos totales acumulados y recuentos en ejecución serán un poco más complicados. – whytheq

+0

Vote por esto -> https://connect.microsoft.com/SQLServer/feedback/details/254393/over-clause-enhancement-request-distinct-clause-for- funciones agregadas Itzik Ben-Gan se remontó en 2007. No ha sucedido – Davos

Respuesta

90

hay una solución muy simple usando dense_rank()

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

Esto le dará exactamente lo que pedían para: El número de UserAccountKeys distintas dentro de cada mes.

+15

Una cosa a tener cuidado con 'dense_rank()' es que contará NULL mientras que 'COUNT (field) OVER' no lo hará. No puedo emplearlo en mi solución debido a esto, pero sigo pensando que es bastante inteligente. – bf2020

+0

Pero estoy buscando un total acumulado de useraccountkeys durante los meses de cada año: ¿no estoy seguro de cómo responde esto? – whytheq

+0

¡Increíble! ¡Muy elegante! –

5

Creo que la única manera de hacer esto en SQL Server 2008R2 es utilizar una consulta correlacionada o aplicar una combinació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 de SQL-Server 2012 utilizando la sintaxis que ha sugerido:

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

sin embargo, aún no se permite el uso de DISTINCT, por lo que si se requiere DISTINCT y/o si la actualización no es una opción, entonces creo que OUTER APPLY es su mejor opción

+0

gracias. Encontré esta [respuesta SO] (http: // stackoverflow.com/questions/860966/calculate-a-running-total-in-sqlserver) que presenta la opción OUTER APPLY que intentaré. ¿Has visto el enfoque de ACTUALIZACIÓN en bucle en esa respuesta ... está bastante lejos y aparentemente rápido. La vida será más fácil en 2012: ¿es una copia directa de Oracle? – whytheq

2

Uso una solución que es similar a la de David anterior, pero con un giro adicional si algunas filas se deben excluir de la cuenta. 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 

An SQL Fiddle with an extended example can be found here.

+0

Su idea se puede usar para hacer la fórmula original (sin las complejidades de '[Include]' de las que está hablando en su respuesta) con 'dense_rank()' trabajo cuando 'UserAccountKey' puede ser' NULL'. Agregue este término a la fórmula: '-MAX (CASO CUANDO UserAccountKey ES NULO ENTONCES 1 ELSE 0 END) OVER (PARTICIÓN POR Mth)'. –

1

Necromancing:

Es relativiely sencilla para emular un recuento diferente sobre PARTITION BY con MAX a través 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 
Cuestiones relacionadas