PostgreSQL中的延迟检查约束

时间:2013-05-01 17:32:26

标签: sql postgresql deferred deferrable-constraint

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

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中做同样的事情?

4 个答案:

答案 0 :(得分:15)

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

  

DEFERRABLE
  NOT DEFERRABLE

     

这可以控制约束是否可以   推迟。将检查不可延迟的约束   每个命令后立即。检查是否存在约束   延期可以推迟到交易结束(使用   SET CONSTRAINTS命令)。 NOT DEFERRABLE是默认值。   目前,只有UNIQUE,PRIMARY KEY,EXCLUDE和REFERENCES(国外   key)约束接受这个条款。 NOT NULL和CHECK约束   是不可推迟的。

     

INITIALLY IMMEDIATE
  INITIALLY DEFERRED

     

如果约束可以推迟,   此子句指定检查约束的默认时间。如果   约束是INITIALLY IMMEDIATE,在每个语句后检查。   这是默认值。如果约束是最初的延迟,那就是   仅在交易结束时检查。约束检查时间   可以使用SET CONSTRAINTS命令进行更改。

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

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

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

  1. member_details
  2. 中创建成员
  3. address中为第1步
  4. 中的成员创建地址
  5. 提交(没有错误)
    1. member_details
    2. 中创建成员
    3. 提交(并从defferred外键中获取错误)。

答案 1 :(得分: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问题。例如。当一个人转到公司B时,一个未经训练的秘书改变公司和公司A所有老板联系人的地址。是的,当用户表现与Outlook不同时,看到它发生了真实......

无论如何,而且fwiw,我发现将这些东西存放在与联系人相同的表格中通常更方便,即地址1,地址2,电子邮件1,电子邮件2等。这使得其他各种事情变得更加简单。原因 - 即运行您正在研究的支票。在极少数情况下,你想要存储两个以上这样的信息,实际上根本不值得麻烦。

答案 2 :(得分:1)

这就是我想出来的。

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时,它还会返回不同的细节。因此,它需要另一个约束,所以我继续使用触发器并在插入之前将其删除并在插入后重新创建它,因为触发器不会延迟。

答案 3 :(得分:0)

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;