POSTGRESQL外键引用两个不同表的主键

Posted

技术标签:

【中文标题】POSTGRESQL外键引用两个不同表的主键【英文标题】:POSTGRESQL Foreign Key Referencing Primary Keys of two Different Tables 【发布时间】:2021-09-12 03:44:57 【问题描述】:

我有两个表 Books 和 Audiobooks,它们都以 ISBN 作为主键。我有一个 writeby 表,它有一个 isbn 属性,该属性对书籍和有声读物 ISBN 具有外键约束。

当我插入 writtenby 时出现的问题是 postgresql 希望我插入 writtenby 的 ISBN 同时出现在书籍和有声读物中。

对我来说,有一个表 writtenby 来存储作者和他们编写的书籍/有声读物是有意义的,但是这不会转换为 postgresql 中的表。

我正在考虑实施的替代解决方案是建立两个新关系audiobook_writtenbybooks_writtenby,但我不确定这是一个好的替代方案。

您能否告诉我如何实现我最初的想法,即让一个表writtenby 引用两个不同的表,或者如何更好地设计我的数据库?如果您需要更多信息,请告诉我。

【问题讨论】:

【参考方案1】:

在这个具体的例子中,绝对不需要使用多个表。只需使用表“Book”并添加“AudioBook”中的列(如果适用)。如果您必须在表级别上区分非常具体的列,请创建视图。您是否检查过内容相同的“图书”和“有声读物”是否具有相同的 ISBN?

【讨论】:

尽管您的回答在技术上是正确的,但我认为不应该遵循它。 PostgreSQL 非常好地支持这个模型。将多个对象折叠到一个表中通常会导致一团糟。 这种方法的一些问题是:稀疏表(一行中有很多空列),可维护性(查看表并查看与所有行不相关的列会令人困惑) , 性能(与稀疏表有关)【参考方案2】:

在 PostgreSQL 中有不止一种方法可以做到这一点。就个人而言,我更喜欢这种方式。

-- This table should contain all the columns common to both 
-- audio books and printed books.
create table books (
  isbn char(13) primary key,
  title varchar(100) not null,
  book_type char(1) not null default 'p'
    check(book_type in ('a', 'p')),
  -- This unique constraint lets the tables books_printed and books_audio 
  -- target the isbn *and* the type in a foreign key constraint.
  -- This prevents you from having an audio book in this table 
  -- linked to a printed book in another table.
  unique (isbn, book_type)
);

-- Columns unique to printed books.
create table books_printed (
  isbn char(13) primary key references books (isbn),
  -- Allows only one value. This plus the FK constraint below guarantee
  -- that this row will relate to a printed book row, not an audio book
  -- row, in the table books. The table "books_audio" is similar.
  book_type char(1) default 'p'
    check (book_type = 'p'),
  foreign key (isbn, book_type) references books (isbn, book_type),
  other_columns_for_printed_books char(1) default '?'
);

-- Columns unique to audio books.
create table books_audio (
  isbn char(13) primary key references books (isbn),
  book_type char(1) default 'a'
    check (book_type = 'a'),
  foreign key (isbn, book_type) references books (isbn, book_type),
  other_columns_for_audio_books char(1) default '?'
);

-- Authors are common to both audio and printed books, so the isbn here
-- references the table of books.
create table book_authors (
  isbn char(13) not null references books (isbn),
  author_id integer not null references authors (author_id), -- not shown
  primary key (isbn, author_id)
);

【讨论】:

你是真正的mvp,这种方法有什么已知的限制吗? @Weier:通用外键既不是关系概念也不是 SQL 概念。在任何情况下,我发布的架构中的外键都不是文章定义该术语的意义上的通用外键。 @MikeSherrill'CatRecall' 你是对的。我读您的代码太快了,并认为您提出的概念与那些“通用 FK”中实现的概念相同。 edit ^:级联确实有效,我只需将on delete cascade 也添加到 PK 行。 @hazer_hazer:“使用语言的优点;避免使用不好的。” (The Elements of Programming Style,第 2 版,Kernighan 和 Plauger,p19)IMO,PostgreSQL 中的枚举是一个糟糕的枚举。除了我上面给出的原因(还有更多)之外,您可以争辩说它们不适合关系模型,关系模型要求所有数据库数据都表示为表中的行列。话虽如此,可以对 PostgreSQL 做任何你想做的事情。但是你不能说服我枚举在这里是个好主意。【参考方案3】:

RDBMS 不支持多态外键约束。您想要做的事情是合理的,但不是关系模型很好地适应的事情,也是制作 ORM 系统时对象关系阻抗不匹配的真正问题之一。 Nice discussion on this on Ward's WIki

解决您的问题的一种方法可能是创建一个单独的表 known_isbns,并在书籍和有声读物上设置约束和/或触发器,以便该表包含两种特定类型书籍表的所有有效 isbns。然后你对writtenby的FK约束将检查known_isbns。

【讨论】:

关系模型和SQL数据库实际上很好地处理了这种事情。问题不在于关系或 SQL。问题是明显的约束之一实施不正确。 (限制是书籍和有声读物的 ISBN 来自同一个域。)【参考方案4】:

您可以使用表继承来获得两全其美的效果。使用引用 writeby 表的 INHERITS 子句创建 audiobook_writtenby 和 books_writtenby。正如您所描述的,外键可以在子级别定义,但您仍然可以在更高级别引用数据。 (您也可以使用视图来执行此操作,但听起来在这种情况下继承可能更简洁。)

查看文档:

http://www.postgresql.org/docs/current/interactive/sql-createtable.html

http://www.postgresql.org/docs/current/interactive/tutorial-inheritance.html

http://www.postgresql.org/docs/current/interactive/ddl-inherit.html

请注意,如果您这样做,您可能希望在 writeby 表上添加一个 BEFORE INSERT 触发器。

【讨论】:

以上是关于POSTGRESQL外键引用两个不同表的主键的主要内容,如果未能解决你的问题,请参考以下文章

postgreSQL的主外键

是否可以将一个表中的主键引用为超过 2 个表的外键约束?

使用两个表的主键的外键关系

SQL怎么在有外键的主键表中插数据

答辩6

做java项目时的主键和外键是啥啊?