Объедините две таблицы событий в одну временную шкалу

12

Даны две таблицы:

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

Я хочу написать запрос, который возвращает значения для ts, fooи barкоторый представляет собой единое представление самых последних значений. Другими словами, если fooсодержится:

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

и barсодержит:

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

Я хочу запрос, который возвращает:

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

Если в обеих таблицах одновременно происходит событие, порядок не имеет значения.

Я смог создать необходимую структуру, используя объединение всех и фиктивные значения:

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

что даст мне линейную шкалу времени новых значений, но я не совсем могу понять, как заполнить нулевые значения на основе предыдущих строк. Я пробовал lagоконную функцию, но AFAICT будет смотреть только на предыдущий ряд, а не рекурсивно назад. Я смотрел на рекурсивные CTE, но я не совсем уверен, как настроить условия начала и завершения.

Кристофер Керри
источник
Являются ли значения fooи barстрого возрастают с течением времени или тестовый пример вводит в заблуждение в этом отношении?
Эрвин Брандштеттер,
2
Чтобы спасти кого-либо еще, хлопот, sqlfiddle.com/#!15/511414
Крейг Рингер,
1
Вместо того, чтобы менять характер вопроса после получения ответа, задайте новый вопрос . Вы всегда можете связаться с этим для справки. (Вы можете даже предоставить свой собственный ответ, если он у вас есть.) Оригинальная версия должна быть интересна широкой публике. Давайте не будем упаковать много в одном вопросе.
Эрвин Брандштеттер
Извините за перегрузку. Я удалил продолжение и добавил его как новый вопрос .
Кристофер Керри

Ответы:

7

Используйте FULL [OUTER] JOIN, в сочетании с двумя раундами оконных функций :

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;

Поскольку count()значения NULL не учитываются, удобно увеличиваться только с каждым ненулевым значением, тем самым формируя группы с одинаковым значением. Во внешнем SELECT, min()(или max()) также игнорирует значения NULL, тем самым выбирая значение один ненулевой для каждой группы. Вуаля.

Родственный FULL JOINслучай:

Это один из тех случаев, когда процедурное решение может быть просто быстрее, поскольку оно может выполнить работу за одно сканирование. Как эта функция 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;

Вызов:

SELECT * FROM f_merge_foobar();

ДБ <> возиться здесь , демонстрируя оба.

Соответствующий ответ, объясняющий #variable_conflict use_column:

Эрвин Брандштеттер
источник
Интересная проблема не так ли. Я думаю, что эффективное решение, вероятно, требует создания coalesceподобной оконной функции.
Крейг Рингер,
@CraigRinger: Действительно. Я желаю, удивляюсь, думаю ... что это как-то возможно без подзапроса, но мне не удалось найти выход. Это один из тех случаев, когда функция plpgsql будет работать быстрее, поскольку она может сканировать каждую таблицу один раз.
Эрвин Брандштеттер
@Christopher: Мне было бы интересно узнать производительность каждого варианта в вашей настройке. EXPLAIN ANALYZEлучше 5 ...?
Эрвин Брандштеттер,
2
Жаль, что Postgres еще не реализовал IGNORE NULLS(как Oracle: sqlfiddle.com/#!4/fab35/1 ).
ypercubeᵀᴹ
1
@ypercube: Да, Oracle simple вообще не хранит значения NULL и, следовательно, не может отличить ''NULL от разницы .
Эрвин Брандштеттер