Kombinieren Sie zwei Ereignistabellen in einer einzigen Zeitleiste

12

Gegeben zwei Tabellen:

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

Ich möchte eine Abfrage schreiben , dass die Renditen Werte für ts, foound bardass eine einheitliche Sicht auf die jüngsten Werte darstellt. Mit anderen Worten, falls fooenthalten:

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

und barenthielt:

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

Ich möchte eine Abfrage, die Folgendes zurückgibt:

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

Wenn beide Tabellen gleichzeitig ein Ereignis haben, spielt die Reihenfolge keine Rolle.

Ich konnte die benötigte Struktur mit union all und Dummy-Werten erstellen:

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

Das gibt mir eine lineare Zeitleiste mit neuen Werten, aber ich bin nicht in der Lage, die Nullwerte basierend auf den vorherigen Zeilen zu füllen. Ich habe die lagFensterfunktion ausprobiert , aber AFAICT wird nur die vorherige Zeile angezeigt, nicht rekursiv rückwärts. Ich habe rekursive CTEs untersucht, bin mir jedoch nicht ganz sicher, wie die Start- und Beendigungsbedingungen eingerichtet werden sollen.

Christopher Currie
quelle
Steigen die Werte zeitlich foound barstreng an oder ist der Testfall in dieser Hinsicht irreführend?
Erwin Brandstetter,
2
Um anderen den Ärger zu ersparen, sqlfiddle.com/#!15/511414
Craig Ringer
1
Anstatt die Art der Frage zu ändern, nachdem eine Antwort gegeben wurde, stellen Sie bitte eine neue Frage . Sie können jederzeit einen Link zu diesem Link erstellen. (Sie können sogar Ihre eigene Antwort geben, wenn Sie eine haben.) Die Originalversion sollte für die breite Öffentlichkeit interessant sein. Lassen Sie uns nicht zu viel in einer einzigen Frage packen.
Erwin Brandstetter
Entschuldigung für die Überlastung. Ich habe das Follow-up entfernt und es als neue Frage hinzugefügt .
Christopher Currie

Antworten:

7

Verwenden Sie eine FULL [OUTER] JOIN, kombiniert mit zwei Runden von Fensterfunktionen :

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;

Da count()NULL-Werte nicht gezählt werden, nimmt der Wert nur mit jedem Nicht-Null-Wert zu, wodurch Gruppen gebildet werden, die denselben Wert haben. In den äußeren SELECT, min()(oder max()) ignoriert ebenfalls Nullwerte, um dadurch den eine Nicht-Null - Wert pro Gruppe Kommissionieren. Voilá.

Zugehöriger FULL JOINFall:

Dies ist einer der Fälle, in denen eine prozedurale Lösung möglicherweise schneller ist, da die Aufgabe mit einem einzigen Scan erledigt werden kann. Wie diese plpgsql Funktion :

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;

Anruf:

SELECT * FROM f_merge_foobar();

db <> fummeln hier und demonstrieren beides.

Verwandte Antwort zur Erläuterung der #variable_conflict use_column:

Erwin Brandstetter
quelle
Interessantes Problem, nicht wahr? Ich denke, eine effiziente Lösung erfordert wahrscheinlich die Erstellung einer coalesceähnlichen Fensterfunktion.
Craig Ringer
@CraigRinger: In der Tat. Ich stelle fest, dass ich mir wünsche, wundere, denke ... dass dies irgendwie ohne Unterfrage möglich sein sollte, aber ich habe keinen Weg gefunden. Dies ist einer der Fälle, in denen eine plpgsql-Funktion schneller ist, da sie jede Tabelle einmal scannen kann.
Erwin Brandstetter
@ Christopher: Ich würde mich für die Leistung jeder Variante in Ihrem Setup interessieren. EXPLAIN ANALYZE, das beste von 5 ...?
Erwin Brandstetter,
2
Schade, dass Postgres noch nicht implementiert hat IGNORE NULLS(wie Oracle: sqlfiddle.com/#!4/fab35/1 ).
ypercubeᵀᴹ
1
@ypercube: Ja, Oracle simple speichert überhaupt keine NULL-Werte und kann daher den Unterschied zwischen ''und NULL nicht erkennen.
Erwin Brandstetter