PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Fonction de fenêtre PostgreSQL :partitionner par comparaison

En utilisant plusieurs fonctions de fenêtre différentes et deux sous-requêtes, cela devrait fonctionner assez rapidement :

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;

Utilisation de ts comme nom pour la colonne d'horodatage.
En supposant ts être unique - et indexé (une contrainte unique le fait automatiquement).

Dans un test avec une table réelle avec 50 000 lignes, il n'a fallu qu'un seul parcours d'index . Donc, devrait être assez rapide même avec de grandes tables. En comparaison, votre requête avec join/distinct ne s'est pas terminée au bout d'une minute (comme prévu).
Même une version optimisée, traitant une jointure croisée à la fois (la jointure gauche avec à peine une condition limitative est effectivement une jointure croisée) ne s'est pas terminé après une minute.

Pour de meilleures performances avec une grande table, réglez vos paramètres de mémoire, en particulier pour work_mem (pour les grandes opérations de tri). Envisagez de le régler temporairement (beaucoup) plus haut pour votre session si vous pouvez économiser de la RAM. En savoir plus ici et ici.

Comment ?

  1. Dans la sous-requête sub1 regardez l'événement de la ligne précédente et ne le conservez que s'il a changé, marquant ainsi le premier élément d'un nouveau groupe. En même temps, obtenez le id de la ligne précédente et suivante (pre_id , post_id ).

  2. Dans la sous-requête sub2 , count() ne compte que les valeurs non nulles. Le résultat grp marque les pairs dans des blocs d'événements identiques consécutifs.

  3. Dans le dernier SELECT , prenez le premier pre_id et le dernier post_id par groupe pour chaque ligne pour arriver au résultat souhaité.
    En fait, cela devrait être encore plus rapide dans le SELECT externe :

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

    ... puisque l'ordre de tri de la fenêtre correspond à la fenêtre pour pre_id , donc un seul tri est nécessaire. Un test rapide semble le confirmer. En savoir plus sur cette définition de cadre.

SQL Fiddle.