Combina due tabelle di eventi in un'unica sequenza temporale


12

Dato due tabelle:

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

Vorrei scrivere una domanda che ritorna per valori ts, fooe barche rappresenta una visione unificata dei valori più recenti. In altre parole, se foocontenuto:

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

e barconteneva:

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

Voglio una query che ritorni:

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

Se entrambe le tabelle hanno un evento contemporaneamente, l'ordine non ha importanza.

Sono stato in grado di creare la struttura necessaria usando unione tutti e valori fittizi:

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

che mi darà una linea temporale lineare di nuovi valori, ma non sono del tutto in grado di capire come popolare i valori null in base alle righe precedenti. Ho provato la lagfunzione finestra, ma AFAICT guarderà solo alla riga precedente, non ricorsivamente all'indietro. Ho esaminato i CTE ricorsivi, ma non sono sicuro di come impostare le condizioni di inizio e fine.


Sono valori in fooe barrigorosamente ascendente nel tempo o è il caso di test fuorvianti a questo proposito?
Erwin Brandstetter,

2
Per salvare chiunque altro la seccatura, sqlfiddle.com/#!15/511414
Craig Ringer,

1
Invece di cambiare la natura della domanda dopo aver ricevuto una risposta, si prega di porre una nuova domanda . Puoi sempre collegarti a questo come riferimento. (Puoi anche fornire la tua risposta se ne hai una.) La versione originale dovrebbe essere interessante per il pubblico. Non impacchettiamo troppo in una sola domanda.
Erwin Brandstetter,

Ci scusiamo per il sovraccarico. Ho rimosso il follow-up e l'ho aggiunto come nuova domanda .
Christopher Currie,

Risposte:


7

Usa a FULL [OUTER] JOIN, combinato con due giri di funzioni finestra :

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;

Poiché count()non conta i valori NULL, aumenta comodamente solo con ogni valore non nullo, formando così gruppi che condivideranno lo stesso valore. Allo stesso modo SELECT, min()(o max()) ignora allo stesso modo i valori NULL, selezionando quindi un valore non nullo per gruppo. Ecco.

FULL JOINCaso correlato :

È uno di quei casi in cui una soluzione procedurale potrebbe essere più veloce, poiché può svolgere il lavoro in un'unica scansione. Come questa funzione 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;

Chiamata:

SELECT * FROM f_merge_foobar();

db <> violino qui , dimostrando entrambi.

Risposta correlata che spiega #variable_conflict use_column:


Problema interessante non è vero. Penso che una soluzione efficiente probabilmente richieda la creazione di una coalescefunzione finestra simile a quella.
Craig Ringer,

@CraigRinger: Davvero. Mi ritrovo a desiderare, a chiedermi, a pensare ... che in qualche modo dovrebbe essere possibile senza subquery, ma non sono riuscito a trovare un modo. È uno di quei casi in cui una funzione plpgsql sarà più veloce poiché può scansionare ogni tabella una volta.
Erwin Brandstetter,

@Christopher: Sarei interessato alle prestazioni di ogni variante del tuo setup. EXPLAIN ANALYZE, meglio di 5 ...?
Erwin Brandstetter,

2
Peccato che Postgres non abbia ancora implementato IGNORE NULLS(come Oracle ha: sqlfiddle.com/#!4/fab35/1 ).
ypercubeᵀᴹ

1
@ypercube: Sì, Oracle simple non memorizza affatto i valori NULL e di conseguenza non è in grado di distinguere tra ''NULL.
Erwin Brandstetter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.