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 ?
-
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 leid
de la ligne précédente et suivante (pre_id
,post_id
). -
Dans la sous-requête
sub2
,count()
ne compte que les valeurs non nulles. Le résultatgrp
marque les pairs dans des blocs d'événements identiques consécutifs. -
Dans le dernier
SELECT
, prenez le premierpre_id
et le dernierpost_id
par groupe pour chaque ligne pour arriver au résultat souhaité.
En fait, cela devrait être encore plus rapide dans leSELECT
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.