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

发布于 2024-12-21 04:26:15 字数 1896 浏览 2 评论 0原文

我是 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”详细信息:密钥 (person_id)=(1) 是 不存在于表“person”中。

我在 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

I'm new to PostgreSQL. I have tables like:

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),
);

According to these tables, when I want to INSERT data into Adress table, Postgres gives that error:

ERROR: insert or update on table "address" violates foreign key
constraint "address_person_id_fkey" DETAIL: Key (person_id)=(1) is
not present in table "person".

I've learned that in Postgres

indexes (including unique constraints) and foreign key constraints
only apply to single tables, not to their inheritance children.

My question is how can I fix this with using triggers? Sample code would be very useful.

After inserting a few rows to child tables, I can see the data with 'SELECT * FROM Person;' as well. It looks like:

Person Table

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"

Student Table

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"

Employee Table

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

牵你的手,一向走下去 2024-12-28 04:26:15

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

alter table address drop constraint address_person_id_fkey

如果抱怨没有 address_person_id_fkey 约束,那么在 psql 中使用 \d address;找出 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 触发器。

参考文献:

First get rid of the FK with something like this:

alter table address drop constraint address_person_id_fkey

If that complains about there not being an address_person_id_fkey constraint then use \d address; in psql to find out what the FK is called.

Then a simple trigger like this should do the trick:

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;

And attach it like this:

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

Then you'll get an error like this if you try to add an address for someone that doesn't exist in person (including the tables that inherit from it):

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

You'd want to add a BEFORE DELETE trigger to person to avoid dangling references, that basic structure would be pretty much the same. You might want an index on address.person_id to help support the BEFORE DELETE trigger as well.

References:

—━☆沉默づ 2024-12-28 04:26:15

外键不被继承。如果外键指向表person,那么该表中必须有相同的值。继承的实现在 PostgreSQL 中是有限的,我引用了“Caveats”一章 手册中

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

包括@Mu 提议的触发器。为了保证引用完整性,您需要的不仅仅是触发器ON INSERT。我不会尝试那样做。如果删除一个人会发生什么?改ID了吗?

我认为根本不使用继承。如果您仍然想要或必须这样做,我会建议对您的数据模型进行一些更改。

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

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

    约束 Student_pkey 主键(id)
    约束employee_pkey 主键(id)
    

    这还消除了继承树上 id 列中可能重复的两个来源之一。 (另一个是,您仍然可以将 ID 输入到 student 中,这些 ID 存在于 employeeperson 中。继承系统中的另一个警告。所以,切勿手动插入或更改 id。将其保留为列默认值和顺序。

  • 3) “自然”模型将在 < 之间建立 n:m 关系。代码>地址。对于您的模型,我将使用附加表 person_address 来实现它,其中 address_id 引用表 addressperson_id 仅梦想外键约束(原始问题)。

    按照您的方式,一个地址永远不可能有多个人居住。也许这足以满足您的目的。这样,您也可以将整个地址嵌入到人员表中(并让学生和员工继承它),以完全避免外键问题

Foreign keys are not inherited. If the foreign key points to table person, then the same value has to be in that table. The implementation of inheritance is limited in PostgreSQL, I quote from the chapter "Caveats" in the manual:

There is no good workaround for this case.

This includes @Mu's proposed trigger. You would need a lot more than a trigger ON INSERT to guarantee referential integrity. I would not try that. What happens if you delete a person? Change it's ID?

I would consider not to use inheritance at all. If you still want or have to, I would propose several changes to your data model.

  • 1) email should not be in the address table, it has nothing to to with an address and everything with the person. Move it to the table person. The reason for the misplacement may be that you want to enforce uniqueness. Another reason not to use inheritance at all.

  • 2) The columns id_student and id_employee are redundant. Use the inherited column id as primary keys instead. Just add a constraint to your child tables:

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

    This also eliminates one of two sources of possible duplicates in the id column over the inheritance tree. (The other one is that you can still enter IDs into student that are present in employee or person. Another caveat in the inheritance system. So, never manually insert or change the id. Leave it to the column default and the sequence.

  • 3) The "natural" model would be to have an n:m relationship between address and person. For your model I would implement that with an additional table person_address where address_id references table address and person_id only dreams of a foreign key constraint (original problem).

    The way you have it, an address can never be inhabited by more than one person. Maybe it's good enough for your purpose. This way you might as well imbed the whole address into the person table (and have student and employee inherit it) to avoid your foreign key problem altogether.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文