18

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

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

4 回答 4

26

您可以在 PostgreSQL 中以与其他 RDBMS 相同的方式延迟约束,但对于当前版本 (9.2),您只能延迟 UNIQUE、PRIMARY KEY、EXCLUDE 和 REFERENCES。手册摘录this page

DEFERRABLE
NOT DEFERRABLE

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

INITIALLY IMMEDIATE
INITIALLY DEFERRED

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

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

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

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

  1. 在 中创建一个成员member_details
  2. address在步骤 1 中为成员创建地址
  3. 提交(没有错误)

或者

  1. 在 中创建一个成员member_details
  2. 提交(并从延迟外键中获取错误)。
于 2013-05-01T18:02:55.777 回答
3

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

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 不同时,它确实发生了......

无论如何,我发现将这些东西存储在与联系人相同的表中通常更方便,即 address1、address2、email1、email2 等。由于各种其他原因,它使其他事情变得更简单——即像您正在调查的那样运行检查。在极少数情况下,您想要存储两个以上这样的信息,实际上根本不值得麻烦。

于 2013-05-02T14:32:33.923 回答
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 时,它也会返回不同的详细信息。因此,它需要另一个约束,所以我一直使用触发器并在插入之前删除它并在插入之后重新创建它,因为触发器没有被延迟。

于 2013-05-02T18:45:19.060 回答
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;
于 2021-04-02T17:50:30.783 回答