11

我有两张桌子:

person:
    id serial primary key,
    name varchar(64) not null

task:
    tenant_id   integer not null references person (id) on delete cascade,
    customer_id integer not null references person (id) on delete restrict

(他们有比这更多的列,但其余的与问题无关。)

问题是,我想在删除task租户时级联删除它person。但是当租户和客户是同一个人时,customer_id外键约束会限制删除。

我的问题有两个部分:

  1. 暂时禁用第二个外键是我唯一的选择吗?
  2. 如果是这样,那么我该如何在 PostgreSQL 中做到这一点?
4

1 回答 1

14

实际上,您创建了一个规则相互矛盾的竞争条件。

我的第一个冲动是检查DEFERRED约束是否有帮助。但它没有任何区别是有道理的。

我发现脚本中首先出现CREATE TABLE的 FK 约束是这场比赛的获胜者。如果ON DELETE CASCADE先到,则级联删除,如果ON DELETE RESTRICT先到,则中止操作。

考虑SQL Fiddle上的演示。

这似乎与oid目录表中的较小值相关pg_constraint

SELECT oid, * FROM pg_constraint WHERE conrelid = 'task'::regclass

但是您的反馈表明,这不是原因。也许pg_attribute.attnum决定比赛。无论哪种方式,只要它没有记录在案的行为,您就不能依靠它在下一个主要版本中保持这种状态。可能值得在 pgsql-general@postgresql.org 上发布一个问题。

与所有这些无关,您需要考虑其他行:即使CASCADE将通过task同时具有tenant_idcustomer_id指向a 的行person,如果任何行仅customer_id引用,它仍然会受到限制person
另一个演示此案例的SQL Fiddle 。

如何禁用约束?

你最好的选择是放弃并重新创建它。在事务中执行所有操作,以确保您不会破坏参照完整性。

BEGIN;

ALTER TABLE task DROP CONSTRAINT task_customer_id_fkey;

DELETE FROM person WHERE id = 3;

ALTER TABLE task ADD CONSTRAINT task_customer_id_fkey
FOREIGN KEY (customer_id) REFERENCES person (id) ON DELETE RESTRICT;

COMMIT;

这会独占锁定表,不适合在多用户环境中进行日常使用。

我怎么知道约束的名称?我从pg_constraint上面演示的。使用显式约束名称开头可能更容易:

CREATE TEMP TABLE task (
    customer_id integer NOT NULL
   ,tenant_id integer NOT NULL REFERENCES person (id) ON DELETE CASCADE
   ,CONSTRAINT task_customer_id_fkey FOREIGN KEY (customer_id)
    REFERENCES person (id) ON DELETE RESTRICT
);

还有

ALTER TABLE task DISABLE trigger ALL;

更多的手册在这里。但这会禁用所有触发器。我没有运气尝试仅禁用系统创建的触发器来实现单个 FK 约束。

其他替代方法是使用触发器规则来实施您的制度。这可以正常工作,但这些执行不如外键严格。

于 2013-02-21T17:56:53.690 回答