通过触发器实现行不变性

时间:2015-05-12 13:36:13

标签: postgresql

我有一个表格,其定义类似于以下内容(为了清楚起见,缩写):

CREATE TABLE fns(
  id serial,
  start_date timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
  end_date timestamptz,
  name text NOT NULL,
  parent_id integer,
  PRIMARY KEY (id),
  FOREIGN KEY (parent_id) REFERENCES fns(id),
  UNIQUE(name)
);

UPDATE发生时,我希望“更新”行将end_date设置为CURRENT_TIMESTAMP并创建新行(基于旧行)将start_date设置为CURRENT_TIMESTAMP。例如:

UPDATE之前

| id |              start_date | end_date |  name | parent_id |
|----|-------------------------|----------|-------|-----------|
|  1 | April, 01 2015 00:00:00 |   (null) | fns_a |    (null) |

UPDATE

之后的所需状态
| id |              start_date |                end_date |        name | parent_id |
|----|-------------------------|-------------------------|-------------|-----------|
|  1 | April, 01 2015 00:00:00 | April, 02 2015 00:00:00 | fns_a [old] |    (null) |
|  2 | April, 02 2015 00:00:00 |                  (null) |       fns_a |         1 |

我遇到了name列的唯一约束问题。这是我的触发器的当前状态:

CREATE OR REPLACE FUNCTION enfore_fns_immutability() RETURNS trigger AS $func$
BEGIN
  -- 'Turn off' old record.
  OLD.end_date = CURRENT_TIMESTAMP;
  OLD.name = OLD.name || ' [old]';

  -- Create the new record.
  INSERT INTO fns(start_date, name, parent_id)
    VALUES(CURRENT_TIMESTAMP, NEW.name, OLD.id); -- <-- unique violation

  RETURN OLD;
END
$func$ LANGUAGE plpgsql;

CREATE TRIGGER tg_fns_bi
  BEFORE UPDATE ON fns
  FOR EACH ROW
  EXECUTE PROCEDURE enforce_fns_immutability();

据我所知,这是失败的,因为尚未发生OLD.name的更新,因为包含的事务尚未提交。我正在努力想办法解决它,但感觉必须有一个优雅的解决方案!我考虑过的一些解决方案:

  • 临时表(感觉这个用例太重了)。
  • 使用AFTER UPDATE触发器(与事务相同的问题显然尚未提交)。

我正在使用 Postgres 9.4.1

1 个答案:

答案 0 :(得分:4)

您可以将唯一约束创建为延迟,在这种情况下,您将在commit交易时检查,而不是在执行insert时检查:

CREATE TABLE fns
(
  id serial,
  start_date timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
  end_date timestamptz,
  name text NOT NULL,
  parent_id integer,
  PRIMARY KEY (id),
  FOREIGN KEY (parent_id) REFERENCES fns(id),
  UNIQUE(name) deferrable initially deferred --<< here
);