PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Selecione grupos de usuários distintos por intervalo de tempo

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 colunas week_beg e month_beg são 100% redundantes e podem ser facilmente substituídos pordate_trunc('week', date + 1) - 1 e date_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 com ORDER BY no OVER cláusula usa é RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW . Isso é exatamente o que você precisa.

  • Use UNION ALL , não UNION .

  • Sua escolha infeliz para time_series (D, W, M) não ordena bem, renomeei para fazer o ORDER 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 sobre DISTINCT 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).