【问题标题】:Enforcing row immutability via trigger通过触发器强制行不变性
【发布时间】:2015-05-12 22:00:56
【问题描述】:

我有一个表,其定义类似于以下内容(为清楚起见进行了精简):

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_dateCURRENT_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

【问题讨论】:

  • 有一个来自@RadekPostołowicz 的已删除答案,我认为这也是有效的:您还可以使用条件@ 创建部分唯一索引(与唯一约束相反) 987654335@。这样您甚至可以保留原始名称,而无需在其上附加 [old]
  • @a_horse_with_no_name 谢谢 :) 我认为部分唯一约束对我们不起作用,因为有两次编辑。在这种情况下,我们将有两条记录,其中 parent_id is null.
  • 顺便说一句,在实际代码中,我们在修改后的名称中使用了时间戳组件,因此我们最终不会有两个被称为 fns_a [old] :-)

标签: postgresql


【解决方案1】:

您可以将唯一约束创建为延迟,在这种情况下,它将在您 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
);

【讨论】:

  • 出于兴趣,为什么需要INITIALLY?是不是因为行为可以被SET CONSTRAINTS随后修改?
  • @jabclab:是的。我认为默认值为initially immediate,这意味着您每次运行update 语句时都需要更改它(但即使我错了,默认值为initially deferred,我更愿意清楚地说明我的意图;)
猜你喜欢
  • 1970-01-01
  • 2022-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-21
  • 1970-01-01
  • 2011-03-22
相关资源
最近更新 更多