sql >> Base de Datos >  >> RDS >> PostgreSQL

Función de ventana de PostgreSQL:partición por comparación

Usando varias funciones de ventana diferentes y dos subconsultas, esto debería 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 nombre para la columna de marca de tiempo.
Suponiendo que ts ser único y indexado (una restricción única lo hace automáticamente).

En una prueba con una tabla de la vida real con 50k filas, solo necesitó un único escaneo de índice . Por lo tanto, debería ser decentemente rápido incluso con mesas grandes. En comparación, su consulta con combinación / distinción no finalizó después de un minuto (como se esperaba).
Incluso una versión optimizada, que trata con una combinación cruzada a la vez (la combinación izquierda sin apenas una condición limitante es efectivamente una combinación limitada). unión cruzada) no terminó después de un minuto.

Para obtener el mejor rendimiento con una mesa grande, ajuste la configuración de la memoria, en particular para work_mem (para operaciones de gran orden). Considere configurarlo (mucho) más alto para su sesión temporalmente si puede ahorrar RAM. Lea más aquí y aquí.

¿Cómo?

  1. En la subconsulta sub1 mire el evento de la fila anterior y solo manténgalo si ha cambiado, marcando así el primer elemento de un nuevo grupo. Al mismo tiempo, obtenga el id de la fila anterior y siguiente (pre_id , post_id ).

  2. En la subconsulta sub2 , count() solo cuenta valores no nulos. El grp resultante marca a los compañeros en bloques de los mismos eventos consecutivos.

  3. En el SELECT final , toma el primer pre_id y el último post_id por grupo para cada fila para llegar al resultado deseado.
    En realidad, esto debería ser aún más rápido en el exterior SELECT :

     last_value(post_id) OVER (PARTITION BY grp ORDER BY ts
                               RANGE BETWEEN UNBOUNDED PRECEDING
                                     AND     UNBOUNDED FOLLOWING) AS post_id
    

    ... ya que el orden de clasificación de la ventana coincide con la ventana para pre_id , por lo que solo se necesita una única ordenación. Una prueba rápida parece confirmarlo. Más información sobre esta definición de cuadro.

Violín SQL.