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

时间:2011-12-12 01:51:40

标签: postgresql inheritance database-design triggers 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),
);

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

  

错误:在表“地址”上插入或更新违反外键   约束“address_person_id_fkey”DETAIL:键(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"

学生表

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"

员工表

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

2 个答案:

答案 0 :(得分:5)

外键不是继承的。如果外键指向表person,则该表中必须包含相同的值。 PostgreSQL中继承的实现是有限的,我引用了章节" Caveats" in the manual

  

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

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

我会考虑来使用继承。如果您仍然需要或不得不这样做,我会建议对您的数据模型进行一些更改。

  • 1) email 不应该在地址表中,它与地址以及与此人的所有内容无关。将其移至表格person。错位的原因可能是您想要强制执行唯一性。根本不使用继承的另一个原因。

  • 2)列id_studentid_employee是多余的。请使用继承的列 id 作为主键。只需在子表中添加约束:

    CONSTRAINT student_pkey PRIMARY KEY (id)
    CONSTRAINT employee_pkey PRIMARY KEY (id)
    

    这也消除了继承树上id列中两个可能重复的来源之一。 (另一个是您仍然可以在studentemployee中输入person中的ID。继承系统中的另一个警告。所以,永远不要手动插入或更改{{ 1}}。将其保留为默认列和序列。

  • 3)" natural"模型将是 id address之间的n:m关系。对于您的模型,我将使用额外的表person来实现它,其中address_id引用表person_addressaddress仅梦想外键约束(原始问题)。

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

答案 1 :(得分:4)

首先用这样的东西摆脱FK:

alter table address drop constraint address_person_id_fkey

如果抱怨没有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;

并像这样附上:

create trigger pseudo_fk_for_address_trigger before insert or update on address 
for each row execute procedure pseudo_fk_for_address();

如果您尝试为person中不存在的某个人(包括从其继承的表)添加地址,那么您将收到这样的错误:

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

你想要向person添加一个BEFORE DELETE触发器,以避免悬空引用,基本结构几乎相同。您可能希望address.person_id上的索引也可以帮助支持BEFORE DELETE触发器。

参考文献: