Combinez deux tables d'événements dans une même chronologie

12

Étant donné deux tableaux:

CREATE TABLE foo (ts timestamp, foo text);
CREATE TABLE bar (ts timestamp, bar text);

Je voudrais écrire une requête que les valeurs de rendements pour ts, fooet barqui représente une vue unifiée des valeurs les plus récentes. En d'autres termes, s'ils sont foocontenus:

ts | foo
--------
1  | A
7  | B

et barcontenait:

ts | bar
--------
3  | C
5  | D
9  | E

Je veux une requête qui renvoie:

ts | foo | bar
--------------
1  | A   | null
3  | A   | C
5  | A   | D
7  | B   | D
9  | B   | E

Si les deux tables ont un événement en même temps, l'ordre n'a pas d'importance.

J'ai pu créer la structure nécessaire en utilisant l'union tout et les valeurs fictives:

SELECT ts, foo, null as bar FROM foo
UNION ALL SELECT ts, null as foo, bar FROM bar

ce qui me donnera une chronologie linéaire de nouvelles valeurs, mais je ne suis pas tout à fait capable de déterminer comment remplir les valeurs nulles en fonction des lignes précédentes. J'ai essayé la lagfonction de fenêtre, mais AFAICT ne regardera que la ligne précédente, pas récursivement en arrière. J'ai examiné les CTE récursifs, mais je ne sais pas trop comment configurer les conditions de début et de fin.

Christopher Currie
la source
Les valeurs sont foo-elles barstrictement croissantes dans le temps ou le scénario de test est-il trompeur à cet égard?
Erwin Brandstetter du
2
Pour éviter les tracas à quelqu'un d'autre, sqlfiddle.com/#!15/511414
Craig Ringer
1
Plutôt que de changer la nature de la question après avoir répondu, veuillez poser une nouvelle question . Vous pouvez toujours créer un lien vers celui-ci pour référence. (Vous pouvez même fournir votre propre réponse si vous en avez une.) La version originale devrait être intéressante pour le grand public. N'emballons pas trop dans une seule question.
Erwin Brandstetter
Désolé pour la surcharge. J'ai supprimé le suivi et l'ai ajouté en tant que nouvelle question .
Christopher Currie

Réponses:

7

Utilisez un FULL [OUTER] JOIN, combiné avec deux séries de fonctions de fenêtre :

SELECT ts
     , min(foo) OVER (PARTITION BY foo_grp) AS foo
     , min(bar) OVER (PARTITION BY bar_grp) AS bar
FROM (
   SELECT ts, f.foo, b.bar
        , count(f.foo) OVER (ORDER BY ts) AS foo_grp
        , count(b.bar) OVER (ORDER BY ts) AS bar_grp
   FROM   foo f
   FULL   JOIN bar b USING (ts)
   ) sub;

Étant donné count()que ne compte pas les valeurs NULL, il augmente commodément uniquement avec chaque valeur non nulle, formant ainsi des groupes qui partagent la même valeur. À l'extérieur SELECT, min()(ou max()) ignore également les valeurs NULL, sélectionnant ainsi la seule valeur non nulle par groupe. Voilá.

FULL JOINCas connexe :

C'est l'un de ces cas où une solution procédurale pourrait être plus rapide, car elle peut faire le travail en une seule analyse. Comme cette fonction plpgsql :

CREATE OR REPLACE FUNCTION f_merge_foobar()
  RETURNS TABLE(ts int, foo text, bar text) AS
$func$
#variable_conflict use_column
DECLARE
   last_foo text;
   last_bar text;
BEGIN
   FOR ts, foo, bar IN
      SELECT ts, f.foo, b.bar
      FROM   foo f
      FULL   JOIN bar b USING (ts)
      ORDER  BY 1
   LOOP
      IF foo IS NULL THEN foo := last_foo;
      ELSE                last_foo := foo;
      END IF;

      IF bar IS NULL THEN bar := last_bar;
      ELSE                last_bar := bar;
      END IF;

      RETURN NEXT;
   END LOOP;
END
$func$ LANGUAGE plpgsql;

Appel:

SELECT * FROM f_merge_foobar();

db <> joue ici , démontrant les deux.

Réponse connexe expliquant #variable_conflict use_column:

Erwin Brandstetter
la source
Un problème intéressant n'est-ce pas. Je pense qu'une solution efficace nécessite probablement la création d'une coalescefonction de fenêtre de type.
Craig Ringer
@CraigRinger: En effet. Je me surprends à souhaiter, à me demander, à penser ... que cela devrait être possible en quelque sorte sans sous-requête, mais je n'ai pas réussi à trouver un moyen. C'est l'un de ces cas où une fonction plpgsql sera plus rapide car elle peut analyser chaque table une fois.
Erwin Brandstetter
@Christopher: Je serais intéressé par les performances de chaque variante de votre configuration. EXPLAIN ANALYZE, le meilleur de 5 ...?
Erwin Brandstetter
2
Dommage que Postgres n'ait pas encore implémenté IGNORE NULLS(comme Oracle l'a: sqlfiddle.com/#!4/fab35/1 ).
ypercubeᵀᴹ
1
@ypercube: Oui, Oracle simple ne stocke pas du tout les valeurs NULL et ne peut donc pas faire la différence entre ''et NULL.
Erwin Brandstetter