Об'єднайте дві таблиці подій в одну шкалу часу


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();

db <> скрипте тут , демонструючи обидва.

Відповідна відповідь, що пояснює #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 та NULL.
Erwin Brandstetter
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.