2011-04-18 28 views
45

estoy usando count y group by para obtener el número de abonados registrados cada día:Conde total acumulado en PostgreSQL

SELECT created_at, COUNT(email) 
    FROM subscriptions 
GROUP BY created at; 

Resultado:

created_at count 
----------------- 
04-04-2011 100 
05-04-2011 50 
06-04-2011 50 
07-04-2011 300 

quiero conseguir el total acumulado de suscriptores cada día en cambio. ¿Cómo obtengo esto?

created_at count 
----------------- 
04-04-2011 100 
05-04-2011 150 
06-04-2011 200 
07-04-2011 500 

Respuesta

77

con grandes conjuntos de datos, window functions son la forma más eficiente para llevar a cabo este tipo de consultas - la tabla se escanea una sola vez, en lugar de una vez para cada fecha, como lo haría una auto unión. También parece mucho más simple. :) PostgreSQL 8.4 y posteriores tienen soporte para funciones de ventana.

Esto es lo que parece:

SELECT created_at, sum(count(email)) OVER (ORDER BY created_at) 
FROM subscriptions 
GROUP BY created_at; 

Aquí OVER crea la ventana; ORDER BY created_at significa que tiene que resumir los recuentos en el orden created_at.


Editar: Si desea eliminar correos electrónicos duplicados dentro de un solo día, se puede utilizar sum(count(distinct email)). Lamentablemente, esto no eliminará los duplicados que cruzan diferentes fechas.

Si desea eliminar todos los duplicados, creo que lo más sencillo es utilizar una subconsulta y DISTINCT ON. Esto atribuir correos electrónicos a su fecha más temprana (porque estoy clasificación por created_at en orden ascendente, que va a elegir la más temprana):

SELECT created_at, sum(count(email)) OVER (ORDER BY created_at) 
FROM (
    SELECT DISTINCT ON (email) created_at, email 
    FROM subscriptions ORDER BY email, created_at 
) AS subq 
GROUP BY created_at; 

Si crea un índice en (email, created_at), esta consulta no debe ser demasiado lento tampoco.


(Si desea probar, así es como he creado el conjunto de datos de muestra)

create table subscriptions as 
    select date '2000-04-04' + (i/10000)::int as created_at, 
      '[email protected]' || (i%700000)::text as email 
    from generate_series(1,1000000) i; 
create index on subscriptions (email, created_at); 
+0

Esto es genial intgr, solo que mi la tabla de suscripciones contiene muchas filas de correo electrónico duplicadas. Entonces, lo que 'sobre' está haciendo es 'sumar' los números de 'conteo', pero aún tengo que volver a calcular los correos electrónicos únicos en cada fecha posterior. – Khairul

+0

Actualicé mi respuesta con una subconsulta 'DISTINCT ON'. Todavía es mucho más rápido que la respuesta de Andriy: puede procesar un millón de filas en pocos segundos, pero quizás sea más complicado. – intgr

+0

¡Buen consejo sobre la función generate_series! –

6

Uso:

SELECT a.created_at, 
     (SELECT COUNT(b.email) 
      FROM SUBSCRIPTIONS b 
     WHERE b.created_at <= a.created_at) AS count 
    FROM SUBSCRIPTIONS a 
2
SELECT 
    s1.created_at, 
    COUNT(s2.email) AS cumul_count 
FROM subscriptions s1 
    INNER JOIN subscriptions s2 ON s1.created_at >= s2.created_at 
GROUP BY s1.created_at 
+0

He intentado la 'suma (s2.count) 'y la consola da un error: 'las llamadas a funciones agregadas no pueden anidarse' – Khairul

+0

Quise decir que fuera' COUNT (s2.email) ', lo siento. Por favor, mira mi solución editada. –

+0

Gracias amigo! Estaba trabajando con una consulta más complicada, y su estructura es fácil de entender (y por lo tanto, de implementar). – Khairul

2

Asumo que desea sólo una fila por día y que desea mostrar todavía días sin ningún tipo de suscripciones (supongamos que nadie se suscribe para una fecha determinada, ¿quieres para mostrar esa fecha con el saldo del día anterior?). Si este es el caso, se puede utilizar el 'con' característica:

with recursive serialdates(adate) as (
    select cast('2011-04-04' as date) 
    union all 
    select adate + 1 from serialdates where adate < cast('2011-04-07' as date) 
) 
select D.adate, 
(
    select count(distinct email) 
    from subscriptions 
    where created_at between date_trunc('month', D.adate) and D.adate 
) 
from serialdates D 
+0

Gracias, esa función 'with' podría ser útil también. Aprendí algo nuevo. – Khairul

+2

En lugar de serialdates puede usar la función incorporada: 'generate_series (timestamp '2011-04-04', timestamp '2011-04-07', intervalo '1 day')' – intgr

-3

La mejor manera es tener una tabla de calendario: calendario ( fecha fecha, meses int , trimestre int, int medio, semana int, int año )

Entonces, usted c unase a esta tabla para hacer un resumen del campo que necesita.

+1

Eso no tiene nada que ver con obtener un total acumulado. –