将两个事件表合并为一个时间线

Chr*_*rie 12 postgresql join window-functions

给定两个表:

CREATE TABLE foo (ts timestamp, foo text);
CREATE TABLE bar (ts timestamp, bar text);
Run Code Online (Sandbox Code Playgroud)

我想编写一个查询,对于回报值tsfoo以及bar代表最新的值的统一视图。换句话说,如果foo包含:

ts | foo
--------
1  | A
7  | B
Run Code Online (Sandbox Code Playgroud)

bar包含:

ts | bar
--------
3  | C
5  | D
9  | E
Run Code Online (Sandbox Code Playgroud)

我想要一个返回的查询:

ts | foo | bar
--------------
1  | A   | null
3  | A   | C
5  | A   | D
7  | B   | D
9  | B   | E
Run Code Online (Sandbox Code Playgroud)

如果两个表同时有一个事件,则顺序无关紧要。

我已经能够使用 union all 和 dummy 值创建所需的结构:

SELECT ts, foo, null as bar FROM foo
UNION ALL SELECT ts, null as foo, bar FROM bar
Run Code Online (Sandbox Code Playgroud)

这将为我提供新值的线性时间线,但我不太能弄清楚如何根据前一行填充空值。我已经尝试过lag窗口函数,但 AFAICT 它只会查看前一行,而不是递归地向后查看。我看过递归 CTE,但我不太确定如何设置开始和终止条件。

Erw*_*ter 8

使用a 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;
Run Code Online (Sandbox Code Playgroud)

由于count()不计算 NULL 值,它方便地只随每个非空值增加,从而形成共享相同值的组。在外部SELECT, min()(or max()) 同样忽略 NULL 值,从而为每组选择一个非空值。瞧。

相关FULL JOIN案例:

这是程序解决方案可能更快的情况之一,因为它可以在一次扫描中完成工作。像这个plpgsql 函数

CREATE OR REPLACE FUNCTION f_merge_foobar()
  RETURNS TABLE(ts int, foo text, bar text)
  LANGUAGE plpgsql 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$;
Run Code Online (Sandbox Code Playgroud)

称呼:

SELECT * FROM f_merge_foobar();
Run Code Online (Sandbox Code Playgroud)

db<>fiddle here,展示了两者。
旧的sqlfiddle

相关答案解释了#variable_conflict use_column

  • 遗憾的是 Postgres 还没有实现`IGNORE NULLS`(如 Oracle 有:http://sqlfiddle.com/#!4/fab35/1)。 (2认同)