Contar todos linhas
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
-
Suas colunasweek_beg
emonth_beg
são 100% redundantes e podem ser facilmente substituídos pordate_trunc('week', date + 1) - 1
edate_trunc('month', date)
respectivamente.
-
Sua semana parece começar no domingo (desligado por um), portanto, o+ 1 .. - 1
.
-
O quadro padrão de uma função de janela comORDER BY
noOVER
cláusula usa éRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
. Isso é exatamente o que você precisa.
-
UseUNION ALL
, nãoUNION
.
-
Sua escolha infeliz paratime_series
(D, W, M) não ordena bem, renomeei para fazer oORDER BY
final mais fácil.
-
Essa consulta pode lidar com várias linhas por dia. As contagens incluem todos os pares por um dia.
-
Mais sobreDISTINCT ON
:
DISTINTOS usuários por dia
Para contar cada usuário apenas uma vez por dia, use um CTE com
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
Usuários DISTINTOS durante um período de tempo dinâmico
Você sempre pode recorrer a subconsultas correlacionadas . Tende a ser lento com tabelas grandes!
Com base nas 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 as três soluções.
Mais rápido com dense_rank()
@Clodoaldo surgiu com uma grande melhoria:use a função de janela
dense_rank()
. Aqui está outra ideia para uma versão otimizada. Deve ser ainda mais rápido excluir duplicatas diárias imediatamente. O ganho de desempenho cresce com o número de linhas por dia. Com base em um modelo de dados simplificado e higienizado - sem as colunas redundantes-
day
como nome da coluna em vez de date
date
é uma palavra reservada no SQL padrão
e um nome de tipo básico no PostgreSQL e não deve ser usado como identificador. CREATE TABLE uniques(
day date -- instead of "date"
,user_id int
);
Consulta aprimorada:
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 demonstrando o desempenho de 4 variantes mais rápidas. Depende da sua distribuição de dados qual é a mais rápida para você.
Todos eles são cerca de 10x mais rápidos que a versão de subconsultas correlacionadas (o que não é ruim para subconsultas correlacionadas).