繁体   English   中英

PostgreSQL 中的延迟检查约束

[英]Deferrable check constraint in PostgreSQL

我有如下功能检查强制参与:

CREATE FUNCTION member_in_has_address()
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (SELECT *
       FROM address a, member_details b
       WHERE b.member_id = a.member_id);
END;
$$  LANGUAGE plpgsql;

然后从 CHECK 约束调用

ALTER TABLE member_details
 ADD CONSTRAINT member_in_has_address_check
  CHECK (member_in_has_address());

要在标准 SQL 中创建可延迟约束,它将是:

ALTER TABLE member_details
 ADD CONSTRAINT member_in_has_address_check
  INITIALLY DEFERRED
  CHECK (member_in_has_address()); 

我如何在 PostgreSQL 中做同样的事情?

您可以像在其他 RDBMS 中一样推迟 PostgreSQL 中的约束,但对于当前版本 (9.2),您只能推迟 UNIQUE、PRIMARY KEY、EXCLUDE 和 REFERENCES。 摘自this page手册this page

DEFERRABLE
NOT DEFERRABLE

这控制是否可以推迟约束。 不可延迟的约束将在每个命令后立即检查。 可延迟约束的检查可以推迟到事务结束(使用 SET CONSTRAINTS 命令)。 不可延迟是默认值。 目前,只有 UNIQUE、PRIMARY KEY、EXCLUDE 和 REFERENCES(外键)约束接受此子句。 NOT NULL 和 CHECK 约束不可延迟。

INITIALLY IMMEDIATE
INITIALLY DEFERRED

如果约束是可延迟的,则该子句指定检查约束的默认时间。 如果约束是 INITIALLY IMMEDIATE,则在每个语句之后检查它。 这是默认设置。 如果约束是 INITIALLY DEFERRED,则仅在事务结束时对其进行检查。 可以使用 SET CONSTRAINTS 命令更改约束检查时间。

您可以从member_detailsaddress创建一个简单的延迟外键,而不是您当前的约束来检查,如果每个成员都有一个地址。

更新:您需要创建 2 个外键。 一个从address(member_id)member_details(member_id)常规。 另一个 - 从member_details(member_id)推迟到address(member_id)

使用这两个外键,您将能够:

  1. member_details创建一个成员。
  2. 创建一个地址address从第1步为构件
  3. 提交(没有错误)

要么

  1. member_details创建一个成员。
  2. 提交(并从延迟外键中获取错误)。

将您的查询包装在一个事务中,然后在至少需要一个地址时使用延迟外键和延迟约束触发器:

CREATE CONSTRAINT TRIGGER member_details_address_check_ins
  AFTER INSERT ON member_details
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE member_details_address_check_ins();

ALTER TABLE address
ADD CONSTRAINT address_member_details_member_id_fkey
FOREIGN KEY (member_id) REFERENCES member_details(member_id)
ON UPDATE NO ACTION ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED;

CREATE CONSTRAINT TRIGGER address_member_details_check_del
  AFTER DELETE ON address
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE address_member_details_check_del();

-- also consider the update cases for the inevitable merge of duplicate members.

在单独的注释中,规范化和漂亮,但将地址和联系方式(例如电子邮件)放在单独的地址表中偶尔会引入非常丰富多彩的 UI/UX 问题。 例如,一位未经培训的秘书更改了她在 A 公司的所有老板的联系人的公司和地址,其中一个人切换到了 B 公司。是的,当 UI 的行为与 Outlook 不同时,它确实发生了……

无论如何,顺便说一句,我发现将这些东西与联系人存储在同一个表中通常更方便,即地址 1、地址 2、电子邮件 1、电子邮件 2 等。由于各种其他原因,它使其他事情变得更简单——即像您正在调查的那样运行检查。 您想要存储两条以上这样的信息的极为罕见的情况在实践中根本不值得麻烦。

这就是我想出的。

ALTER TABLE address
ADD CONSTRAINT address_member_in_has_address
FOREIGN KEY (member_id) REFERENCES member_details(member_id)
ON DELETE CASCADE
DEFERRABLE INITIALLY DEFERRED;

CREATE FUNCTION member_in_has_address() RETURNS trigger AS $BODY$
    BEGIN
    IF NOT EXISTS(SELECT * 
                   FROM member_details
                   WHERE member_id IN (SELECT member_id 
                                        FROM address)) 
    THEN
            RAISE EXCEPTION 'Error: member does not have address';
        END IF;
    RETURN NEW;
    END;
$BODY$ LANGUAGE plpgsql;

CREATE CONSTRAINT TRIGGER manatory_participation_member_details_ins
 AFTER INSERT ON member_details 
 DEFERRABLE INITIALLY DEFERRED 
 FOR EACH ROW  
 EXECUTE PROCEDURE member_in_has_address();

CREATE CONSTRAINT TRIGGER manatory_participation_member_details_del
 AFTER INSERT ON member_details 
 DEFERRABLE INITIALLY DEFERRED 
 FOR EACH ROW 
 EXECUTE PROCEDURE member_in_has_address();

我在没有触发器的两个表中使用外键尝试了 Igor 的版本。 在这种情况下,不会推迟此约束。

ALTER TABLE member_details
ADD CONSTRAINT member_details_in_has_address
FOREIGN KEY (address_id) REFERENCES address
ON UPDATE NO ACTION ON DELETE CASCADE
DEFERRABLE INITIALLY DEFERRED;

我得到这个:错误:“address_id”列中的空值违反了非空约束

使用此匿名块插入时:

DO $$ 
DECLARE 
 mem BIGINT;
BEGIN
INSERT INTO member_details (member_first_name, member_last_name, member_dob, member_phone_no, 
member_email, member_gender, industry_position, account_type, music_interests)
VALUES ('Rado','Luptak','07/09/80','07540962233','truba@azet.sk','M','DJ','basic','hard core');

SELECT member_id 
 INTO mem
FROM member_details
WHERE member_first_name = 'Rado' AND member_last_name = 'Luptak'
AND member_dob = '07/09/76';

INSERT INTO address (address_id, house_name_no, post_code, street_name, town, country, member_id)
VALUES (mem, '243', 'E17 3TT','Wood Road','London', 'UK', mem);

UPDATE member_details
 SET  address_id = mem WHERE member_id = mem;
END
$$;

使用地址表(Igor 的版本)的 address_id 强制参与 member_details 的另一个问题是,这允许我将行插入 member_details 并引用现有地址行,但现有地址行引用不同的 member_details 行。 当后面的 member_details 行被删除时,它会级联并删除地址行,地址行可以或不能删除(取决于设置)新插入的 member_details 行。 在加入 member_id 和 address_id 时,它还会返回不同的详细信息。 因此,它需要另一个约束,所以我一直使用触发器并在插入之前删除它并在插入之后重新创建它,因为触发器没有延迟。

2 种测试方法。

1.更改最初推迟的约束。

begin;

alter TABLE t1 alter CONSTRAINT t1_fkey deferrable INITIALLY DEFERRED;

delete from t1;

-- insert into t1 (...)

alter TABLE t1 alter CONSTRAINT t1_fkey not deferrable;

-- commit;
rollback;

2.设置所有延迟的约束。

begin;

alter TABLE t1 alter CONSTRAINT t1_fkey deferrable initially immediate;
SET CONSTRAINTS t1_fkey DEFERRED
-- SET CONSTRAINTS ALL DEFERRED;  -- or, do this.

delete from t1;

-- insert into t1 (...)

alter TABLE t1 alter CONSTRAINT t1_fkey not deferrable;

-- commit;
rollback;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM