在继承的表上使用触发器来替换外键

pik*_*ikk 4 postgresql inheritance triggers database-design referential-integrity

我是PostgreSQL的新手.我有这样的表:

CREATE TABLE Person (
  ID SERIAL PRIMARY KEY,
  Name VARCHAR(32) NOT NULL DEFAULT '',
  Surname VARCHAR(32) NOT NULL DEFAULT '',
  Birthday DATE,
  Gender VARCHAR(8)
);

-- Student table inherits from person
CREATE TABLE Student (
  ID_Student SERIAL PRIMARY KEY,
  MajorDept VARCHAR(32),
) INHERITS(Person);

-- Student table inherits from person
CREATE TABLE Employee (
  ID_Employee SERIAL PRIMARY KEY,
  Position VARCHAR(32),
  Rank VARCHAR(32),
  Salary NUMERIC(12,2)
) INHERITS(Person);

-- Address table references person
CREATE TABLE Address (
  ID_Address SERIAL PRIMARY KEY,
  Person_id INTEGER REFERENCES Person(ID) NOT NULL,
  Email VARCHAR(32) UNIQUE,
  Country VARCHAR(32),
  CityCode INTEGER,
  City VARCHAR(32),
  AddressLine VARCHAR(60),
);
Run Code Online (Sandbox Code Playgroud)

根据这些表,当我想将数据INSERT到Adress表中时,Postgres会给出错误:

错误:在表"地址"上插入或更新违反外键约束"address_person_id_fkey"详细信息:表(person)中不存在键(person_id)=(1).

我在Postgres中学到了这一点

索引(包括唯一约束)和外键约束仅适用于单个表,而不适用于其继承子项.

我的问题是如何使用触发器解决这个问题?示例代码非常有用.

在向子表插入几行后,我可以使用'SELECT*FROM Person;'查看数据.同样.看起来像:

人员表

1;"Bill";"Smith";"1985-05-10";"male"
2;"Jenny";"Brown";"1986-08-12";"female"
3;"Bob";"Morgan";"1986-06-11";"male"
4;"Katniss";"Everdeen";"1970-08-12";"female"
5;"Peter";"Everdeen";"1968-08-12";"male"
Run Code Online (Sandbox Code Playgroud)

学生表

1;"Bill";"Smith";"1985-05-10";"male";1;"chemistry"
2;"Jenny";"Brown";"1986-08-12";"female";2;"physics"
3;"Bob";"Morgan";"1986-06-11";"male";3;"physics"
Run Code Online (Sandbox Code Playgroud)

员工表

4;"Katniss";"Everdeen";"1970-08-12";"female";1;"Prof";"1";3500.00
5;"Peter";"Everdeen";"1968-08-12";"male";2;"Assist-Prof";"5";1800.00
Run Code Online (Sandbox Code Playgroud)

mu *_*ort 5

首先用以下方法摆脱FK:

alter table address drop constraint address_person_id_fkey
Run Code Online (Sandbox Code Playgroud)

如果抱怨不存在作为一个address_person_id_fkey约束,则使用\d address;psql找出的FK被调用。

然后像这样的简单触发器就可以解决问题:

create or replace function pseudo_fk_for_address() returns trigger as $$
begin
    if not exists(select 1 from person where id = new.person_id) then
        raise exception 'No such person: %', new.person_id;
    end if;
    return new;
end;
$$ language plpgsql;
Run Code Online (Sandbox Code Playgroud)

并像这样附加它:

create trigger pseudo_fk_for_address_trigger before insert or update on address 
for each row execute procedure pseudo_fk_for_address();
Run Code Online (Sandbox Code Playgroud)

如果您尝试为不存在的人person(包括从其继承的表)添加地址,则会收到类似的错误消息:

playpen=> insert into address (person_id, email, country, citycode, city, addressline) values (3, 'ab', 'b', 2, 'c', 'd');
ERROR:  No such person: 3
Run Code Online (Sandbox Code Playgroud)

您希望添加一个BEFORE DELETE触发器以person避免悬挂引用,该基本结构几乎相同。您可能希望建立索引address.person_id以帮助支持BEFORE DELETE触发器。

参考文献:


Erw*_*ter 5

外键不是继承的.如果外键指向表person,则该表中必须包含相同的值.PostgreSQL中继承的实现是有限的,我引用手册中的 "注意事项"一章:

这种情况没有好的解决方法.

包括 @Mu提出的触发器.您需要的不仅仅是触发器,ON INSERT以保证参照完整性.我不会那样试试.如果删除某人会怎样?改变它的ID?

我会考虑根本不使用继承.如果您仍然需要或必须,我会建议对您的数据模型进行一些更改.

  • 1)email不应该在地址表中,它与地址和与人的一切无关.把它移到桌子上person.错位的原因可能是您想要强制执行唯一性.根本不使用继承的另一个原因.

  • 2)列id_studentid_employee冗余.请使用继承列id作为主键.只需在子表中添加一个约束:

    CONSTRAINT student_pkey PRIMARY KEY (id)
    CONSTRAINT employee_pkey PRIMARY KEY (id)
    
    Run Code Online (Sandbox Code Playgroud)

    这也消除id了继承树中列中两个可能重复的来源之一.(另一个是你仍然可以输入student存在于employee或中的ID person.继承系统中的另一个警告.所以,永远不要手动插入或更改id.将它保留到列默认和序列.

  • 3)"自然"模型将在address和之间建立n:m关系person.对于你的模型,我将使用一个额外的表来实现它,person_address其中address_id引用表address并且person_id只有外键约束的梦想(原始问题).

    你拥有它的方式,一个地址永远不会被一个人居住.也许这对你的目的来说已经足够了.这样你就可以将整个地址嵌入到人员表中(并让学生和员工继承它)以完全避免你的外键问题.