Combine duas tabelas de eventos em uma única linha do tempo

12

Dadas duas tabelas:

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

Eu gostaria de escrever uma consulta que retorna valores para ts, fooe barque representa uma visão unificada dos valores mais recentes. Em outras palavras, se foocontido:

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

e barcontinha:

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

Eu quero uma consulta que retorne:

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

Se as duas tabelas tiverem um evento ao mesmo tempo, o pedido não importará.

Consegui criar a estrutura necessária usando os valores union all e dummy:

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

que fornecerá uma linha do tempo linear de novos valores, mas não sou capaz de descobrir como preencher os valores nulos com base nas linhas anteriores. Eu tentei a lagfunção de janela, mas AFAICT só vai olhar para a linha anterior, não recursivamente para trás. Eu observei CTEs recursivas, mas não sei ao certo como configurar as condições de início e término.

Christopher Currie
fonte
São valores em fooe barestritamente ascendente ao longo do tempo ou é o caso de teste enganosa a este respeito?
Erwin Brandstetter
2
Para salvar mais ninguém o incômodo, sqlfiddle.com/#!15/511414
Craig Ringer
1
Em vez de alterar a natureza da pergunta depois que uma resposta for dada, faça uma nova pergunta . Você sempre pode vincular a este para referência. (Você pode até fornecer sua própria resposta, se tiver uma.) A versão original deve ser interessante para o público em geral. Não vamos arrumar muito em uma única pergunta.
Erwin Brandstetter
Desculpe pela sobrecarga. Eu removi o acompanhamento e o adicionei como uma nova pergunta .
Christopher Currie

Respostas:

7

Use a FULL [OUTER] JOIN, combinado com duas rodadas de funções da janela :

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;

Como count()não conta valores NULL, convenientemente aumenta apenas com todos os valores não nulos, formando grupos que compartilham o mesmo valor. No exterior SELECT, min()(ou max()) também ignora valores NULL, escolhendo assim um valor não nulo por grupo. Voilá.

FULL JOINCaso relacionado :

É um daqueles casos em que uma solução processual pode ser mais rápida, pois pode fazer o trabalho em uma única digitalização. Como esta função 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;

Ligar:

SELECT * FROM f_merge_foobar();

db <> mexer aqui , demonstrando ambos.

Resposta relacionada que explica o #variable_conflict use_column:

Erwin Brandstetter
fonte
Problema interessante, não é? Eu acho que uma solução eficiente provavelmente requer a criação de uma coalescefunção de janela semelhante.
Craig Ringer
@ CraigRinger: De fato. Me pego desejando, imaginando, pensando ... que isso de alguma forma seja possível sem subconsulta, mas não consegui encontrar um caminho. É um daqueles casos em que uma função plpgsql será mais rápida, pois pode verificar cada tabela uma vez.
Erwin Brandstetter
@ Christopher: Eu estaria interessado no desempenho de cada variante na sua configuração. EXPLAIN ANALYZE, melhor de 5 ...?
Erwin Brandstetter
2
Pena que o Postgres ainda não tenha implementado IGNORE NULLS(como a Oracle: sqlfiddle.com/#!4/fab35/1 ).
precisa saber é o seguinte
1
@ ypercube: Sim, o Oracle simple não armazena valores NULL e, consequentemente, não pode dizer a diferença entre ''e NULL.
Erwin Brandstetter