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

Função de janela do PostgreSQL:partição por comparação


Usando várias funções de janela diferentes e duas subconsultas, isso deve funcionar decentemente rápido:
WITH events(id, event, ts) AS (
  VALUES
   (1, 12, '2014-03-19 08:00:00'::timestamp)
  ,(2, 12, '2014-03-19 08:30:00')
  ,(3, 13, '2014-03-19 09:00:00')
  ,(4, 13, '2014-03-19 09:30:00')
  ,(5, 12, '2014-03-19 10:00:00')
   )
SELECT first_value(pre_id)  OVER (PARTITION BY grp ORDER BY ts)      AS pre_id
     , id, ts
     , first_value(post_id) OVER (PARTITION BY grp ORDER BY ts DESC) AS post_id
FROM  (
   SELECT *, count(step) OVER w AS grp
   FROM  (
      SELECT id, ts
           , NULLIF(lag(event) OVER w, event) AS step
           , lag(id)  OVER w AS pre_id
           , lead(id) OVER w AS post_id
      FROM   events
      WINDOW w AS (ORDER BY ts)
      ) sub1
   WINDOW w AS (ORDER BY ts)
   ) sub2
ORDER  BY ts;

Usando ts como nome para a coluna de carimbo de data/hora.
Supondo ts ser único - e indexado (uma restrição exclusiva faz isso automaticamente).

Em um teste com uma tabela da vida real com 50 mil linhas, foi necessário apenas uma única verificação de índice . Então, deve ser decentemente rápido mesmo com mesas grandes. Em comparação, sua consulta com join / distinct não terminou após um minuto (como esperado).
Mesmo uma versão otimizada, lidando com uma junção cruzada de cada vez (a junção esquerda com quase nenhuma condição limitante é efetivamente uma cross join) não terminou após um minuto.

Para obter o melhor desempenho com uma mesa grande, ajuste suas configurações de memória, especialmente para work_mem (para grandes operações de classificação). Considere configurá-lo (muito) mais alto para sua sessão temporariamente, se você puder poupar a RAM. Leia mais aqui e aqui.

Como?


  1. Na subconsulta sub1 olhe para o evento da linha anterior e mantenha-o apenas se tiver mudado, marcando assim o primeiro elemento de um novo grupo. Ao mesmo tempo, obtenha o id da linha anterior e da próxima (pre_id , post_id ).

  2. Na subconsulta sub2 , count() conta apenas valores não nulos. O grp resultante marca pares em blocos de mesmos eventos consecutivos.

  3. No SELECT final , pegue o primeiro pre_id e o último post_id por grupo para cada linha para chegar ao resultado desejado.
    Na verdade, isso deve ser ainda mais rápido no SELECT externo :
     last_value(post_id) OVER (PARTITION BY grp ORDER BY ts
                               RANGE BETWEEN UNBOUNDED PRECEDING
                                     AND     UNBOUNDED FOLLOWING) AS post_id
    

    ... já que a ordem de classificação da janela está de acordo com a janela para pre_id , portanto, apenas uma única classificação é necessária. Um teste rápido parece confirmar isso. Mais sobre esta definição de quadro.

SQL Fiddle.