Contar todo filas
SELECT date, '1_D' AS time_series, count(DISTINCT user_id) AS cnt
FROM uniques
GROUP BY 1
UNION ALL
SELECT DISTINCT ON (1)
date, '2_W', count(*) OVER (PARTITION BY week_beg ORDER BY date)
FROM uniques
UNION ALL
SELECT DISTINCT ON (1)
date, '3_M', count(*) OVER (PARTITION BY month_beg ORDER BY date)
FROM uniques
ORDER BY 1, time_series
-
Tus columnas
week_beg
ymonth_beg
son 100 % redundantes y se pueden reemplazar fácilmente pordate_trunc('week', date + 1) - 1
ydate_trunc('month', date)
respectivamente. -
Su semana parece comenzar el domingo (después de uno), por lo tanto,
+ 1 .. - 1
. -
Usa
UNION ALL
, noUNION
. -
Tu desafortunada elección para
time_series
(D, W, M) no se ordena bien, cambié el nombre para hacer elORDER BY
final más fácil. -
Esta consulta puede tratar varias filas por día. Los recuentos incluyen a todos los compañeros de un día.
-
Más información sobre
DISTINCT ON
:
Usuarios DISTINTOS por día
Para contar cada usuario solo una vez al día, use un CTE con DISTINCT ON
:
WITH x AS (SELECT DISTINCT ON (1,2) date, user_id FROM uniques)
SELECT date, '1_D' AS time_series, count(user_id) AS cnt
FROM x
GROUP BY 1
UNION ALL
SELECT DISTINCT ON (1)
date, '2_W'
,count(*) OVER (PARTITION BY (date_trunc('week', date + 1)::date - 1)
ORDER BY date)
FROM x
UNION ALL
SELECT DISTINCT ON (1)
date, '3_M'
,count(*) OVER (PARTITION BY date_trunc('month', date) ORDER BY date)
FROM x
ORDER BY 1, 2
Usuarios DISTINTOS durante un período de tiempo dinámico
Siempre puedes recurrir a subconsultas correlacionadas . ¡Tiendes a ser lento con mesas grandes!
Basándonos en las consultas anteriores:
WITH du AS (SELECT date, user_id FROM uniques GROUP BY 1,2)
,d AS (
SELECT date
,(date_trunc('week', date + 1)::date - 1) AS week_beg
,date_trunc('month', date)::date AS month_beg
FROM uniques
GROUP BY 1
)
SELECT date, '1_D' AS time_series, count(user_id) AS cnt
FROM du
GROUP BY 1
UNION ALL
SELECT date, '2_W', (SELECT count(DISTINCT user_id) FROM du
WHERE du.date BETWEEN d.week_beg AND d.date )
FROM d
GROUP BY date, week_beg
UNION ALL
SELECT date, '3_M', (SELECT count(DISTINCT user_id) FROM du
WHERE du.date BETWEEN d.month_beg AND d.date)
FROM d
GROUP BY date, month_beg
ORDER BY 1,2;
SQL Fiddle para las tres soluciones.
Más rápido con dense_rank()
@Clodoaldo
se le ocurrió una mejora importante:use la función de ventana dense_rank()
. Aquí hay otra idea para una versión optimizada. Debería ser aún más rápido excluir los duplicados diarios de inmediato. La ganancia de rendimiento crece con el número de filas por día.
Construyendo sobre un modelo de datos simplificado y depurado - sin las columnas redundantes- day
como nombre de columna en lugar de date
date
es una palabra reservada en SQL estándar
y un nombre de tipo básico en PostgreSQL y no debe usarse como identificador.
CREATE TABLE uniques(
day date -- instead of "date"
,user_id int
);
Consulta mejorada:
WITH du AS (
SELECT DISTINCT ON (1, 2)
day, user_id
,date_trunc('week', day + 1)::date - 1 AS week_beg
,date_trunc('month', day)::date AS month_beg
FROM uniques
)
SELECT day, count(user_id) AS d, max(w) AS w, max(m) AS m
FROM (
SELECT user_id, day
,dense_rank() OVER(PARTITION BY week_beg ORDER BY user_id) AS w
,dense_rank() OVER(PARTITION BY month_beg ORDER BY user_id) AS m
FROM du
) s
GROUP BY day
ORDER BY day;
SQL Fiddle
demostrando el rendimiento de 4 variantes más rápidas. Depende de su distribución de datos, cuál es la más rápida para usted.
Todos ellos son aproximadamente 10 veces más rápidos que la versión de subconsultas correlacionadas (que no está mal para las subconsultas correlacionadas).