有条件地将新记录插入事务 table

Conditional insert of new records into transaction table

我有一个 table 的历史交易,我想从一个新的 table 中插入记录,其中新记录的状态不同于新记录时的旧记录记录是 "valid"(基于两个 table 中的 valid_from 列)。

例如,取standing/transactional table(我就叫它old):

CREATE TABLE old (id serial, key text, valid_from date, status text);
INSERT INTO old (key, valid_from, status)
VALUES
('A', '2014-01-01', 'x'),
('A', '2014-02-01', 'y'),
('A', '2014-03-01', 'z'),
('B', '2014-01-10', 'x'),
('B', '2014-02-15', 'y');

SELECT * FROM old;

 id | key | valid_from | status
----+-----+------------+--------
  1 | A   | 2014-01-01 | x
  2 | A   | 2014-02-01 | y
  3 | A   | 2014-03-01 | z
  4 | B   | 2014-01-10 | x
  5 | B   | 2014-02-15 | y

以及 new table 的更新:

CREATE TABLE new (id serial, key text, valid_from date, status text);
INSERT INTO new (key, valid_from, status)
VALUES
('A', '2014-01-15', 'x'),
('A', '2014-02-15', 'x'),
('A', '2014-03-15', 'z'),
('B', '2014-02-15', 'y');

SELECT * FROM new;

 id | key | valid_from | status
----+-----+------------+--------
  1 | A   | 2014-01-15 | x
  2 | A   | 2014-02-15 | x
  3 | A   | 2014-03-15 | z
  4 | B   | 2014-02-15 | y

理想情况下,我想插入 only 新 table 的第 2 行,因为它的状态与具有相同 key 的记录不同old table 与新记录之前的最近 valid_from 日期。具体来说:

插入后,old table 应如下所示:

SELECT * FROM old ORDER BY key, valid_from;

 id | key | valid_from | status
----+-----+------------+--------
  1 | A   | 2014-01-01 | x
  2 | A   | 2014-02-01 | y
  6 | A   | 2014-02-15 | x
  3 | A   | 2014-03-01 | z
  4 | B   | 2014-01-10 | x
  5 | B   | 2014-02-15 | y

目标:valid_from.[升序排序时,绝不应该有两个具有相同 keystatus 的连续记录=59=]

如果我的想法是简单地插入 key + status 组合的记录,而 old table 中不存在,我会这样做:

INSERT INTO old (key, valid_from, status)
SELECT new.key, new.valid_from, new.status
FROM new LEFT JOIN old USING(key, status)
WHERE old.key IS NULL;

然而,这错过了新 table 中的 id 2,因为旧 table 中的 id 1 具有相同的状态,即使有更新的old table 中的记录确实不同。

无论我尝试什么,我似乎都无法将 new 记录与 只有 最 "recent" old 记录进行比较valid_from < 新记录中的 valid_from。我有一种感觉,我可以通过组合 window 函数 and/or 和 DISTINCT ON 来找到最大值 valid_from,但我似乎无法将两者放在一起。例如,我尝试过类似的东西:

INSERT INTO old(key, valid_from, status)
SELECT n.key, n.valid_from, n.status
FROM new n LEFT JOIN (
    SELECT DISTINCT ON (old.key) old.*
    FROM new
        JOIN old
        ON old.key = new.key
        AND old.valid_from < new.valid_from
        ORDER BY old.key, old.valid_from DESC) o
ON
    n.key = o.key
    AND n.status = o.status
WHERE
    o.key IS NULL;

但是,这最终会插入 new table 中的第一条记录:

 id | key | valid_from | status
----+-----+------------+--------
  1 | A   | 2014-01-01 | x
  6 | A   | 2014-01-15 | x
  2 | A   | 2014-02-01 | y
  7 | A   | 2014-02-15 | x
  3 | A   | 2014-03-01 | z
  4 | B   | 2014-01-10 | x
  8 | B   | 2014-02-15 | y
  5 | B   | 2014-02-15 | y

我正在使用 PostgreSQL 9.3。关于解决这个问题的方法有什么建议吗?提前致谢!

经过更多的尝试,我想我有一个解决方案(虽然可能不是最好的)。步骤的逻辑顺序是:

  1. 在键上加入,仅在新记录的 valid_from 大于旧记录的 valid_from
  2. 的情况下
  3. 对于每个匹配项,包括一个标志,用于指示旧记录的 valid_from 是否是所有候选匹配项中最近的 valid_from(我们称之为 is_most_recent)。我使用 window 函数(不能在 WHERE 子句中使用)来完成此操作。
  4. 确保新的 status 不等于旧的 status
  5. 插入这些记录

这是迄今为止我想到的最好的:

INSERT INTO old (key, valid_from, status)
SELECT key, valid_from, status FROM (
    SELECT
        new.key, 
        new.valid_from, 
        new.status,
        -- Does the new record have a different statusf from the old?
        old.status != new.status AS is_different_status, 
        -- Are we comparing the most "recent" record in the old table?
        old.valid_from = MAX(old.valid_from) OVER (PARTITION BY old.key, new.id)
            AS is_most_recent
    FROM new JOIN old
        ON new.key = old.key
        AND new.valid_from >= old.valid_from) AS to_insert
WHERE is_different_status AND is_most_recent;

我很想知道是否有更好的解决方案。