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

PostgreSQL:executando contagem de linhas para uma consulta 'por minuto'

Retorne apenas minutos com atividade

O mais curto

SELECT DISTINCT
       date_trunc('minute', "when") AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY 1;

Use date_trunc() , ele retorna exatamente o que você precisa.

Não inclua id na consulta, pois você deseja GROUP BY fatias de minutos.

count() é normalmente usado como função agregada simples. Anexando um OVER cláusula torna uma função de janela. Omitir PARTITION BY na definição da janela - você deseja uma contagem em execução em todas as linhas . Por padrão, isso conta da primeira linha até o último par da linha atual, conforme definido por ORDER BY . O manual:

A opção de enquadramento padrão é RANGE UNBOUNDED PRECEDING , que é o mesmo que RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW . Com ORDER BY ,isso define o quadro para que todas as linhas da partição comecem até o último ORDER BY da linha atual par.

E isso é exatamente o que você precisa.

Use count(*) em vez de count(id) . Ele se encaixa melhor na sua pergunta ("contagem de linhas"). Geralmente é um pouco mais rápido do que count(id) . E, embora possamos supor que id é NOT NULL , não foi especificado na pergunta, então count(id) está errado , estritamente falando, porque os valores NULL não são contados com count(id) .

Você não pode GROUP BY fatias de minutos no mesmo nível de consulta. As funções agregadas são aplicadas antes funções de janela, a função de janela count(*) veria apenas 1 linha por minuto dessa maneira.
Você pode, no entanto, SELECT DISTINCT , porque DISTINCT é aplicado depois funções da janela.

ORDER BY 1 é apenas um atalho para ORDER BY date_trunc('minute', "when") aqui.
1 é uma referência de referência posicional para a 1ª expressão no SELECT Lista.

Use to_char() se você precisar formatar o resultado. Como:
SELECT DISTINCT
       to_char(date_trunc('minute', "when"), 'DD.MM.YYYY HH24:MI') AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY date_trunc('minute', "when");

Mais rápido

SELECT minute, sum(minute_ct) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) sub
ORDER  BY 1;

Muito parecido com o acima, mas:

Eu uso uma subconsulta para agregar e contar linhas por minuto. Dessa forma, obtemos 1 linha por minuto sem DISTINCT no SELECT externo .

Use sum() como função de agregação de janela agora para somar as contagens da subconsulta.

Achei isso substancialmente mais rápido com muitas linhas por minuto.

Incluir minutos sem atividade

O mais curto


@GabiMe perguntou em um comentário como obter uma linha para cada minute no período de tempo, incluindo aqueles em que nenhum evento ocorreu (sem linha na tabela base):
SELECT DISTINCT
       minute, count(c.minute) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (SELECT date_trunc('minute', "when") FROM tbl) c(minute) USING (minute)
ORDER  BY 1;

Gere uma linha para cada minuto no intervalo de tempo entre o primeiro e o último evento com generate_series() - aqui diretamente baseado em valores agregados da subconsulta.

LEFT JOIN para todos os timestamps truncados para o minuto e contar. NULL valores (onde não existe nenhuma linha) não são adicionados à contagem em execução.

Mais rápido


Com CTE:
WITH cte AS (
   SELECT date_trunc('minute', "when") AS minute, count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) 
SELECT m.minute
     , COALESCE(sum(cte.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(min(minute), max(minute), interval '1 min')
   FROM   cte
   ) m(minute)
LEFT   JOIN cte USING (minute)
ORDER  BY 1;

Novamente, agregue e conte linhas por minuto na primeira etapa, omitindo a necessidade de DISTINCT posterior .

Diferente de count() , sum() pode retornar NULL . Padrão para 0 com COALESCE .

Com muitas linhas e um índice em "when" esta versão com uma subconsulta foi a mais rápida entre algumas variantes que testei com o Postgres 9.1 - 9.4:
SELECT m.minute
     , COALESCE(sum(c.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) c USING (minute)
ORDER  BY 1;