【问题标题】:Foreign key constraint with some column values residing in other tables某些列值位于其他表中的外键约束
【发布时间】:2014-02-26 03:05:20
【问题描述】:

当部分 FK 列位于另一个表中时,在 PostgreSQL 中表达外键约束的正确/惯用方式是什么?

我将使用一个示例来说明这一点(省略一些明显的 PK 和 FK 以使其简短)。我们希望对书籍、书籍中的主题、阅读活动(阅读书籍)和阅读活动中讨论的主题(应该是书籍主题的子集)之间的以下关联进行建模:

  book ←———————————— reading_event 
   ↑                     ↑
 theme ←———————————— themeDiscussed

在 SQL 方面,我们有一个用来存放书籍的表:

CREATE TABLE BOOK (name VARCHAR);
INSERT INTO BOOK(name) VALUES('game of thrones');

然后是一张表格,用于存放我们在每本书中找到的各种主题:

CREATE TABLE BOOK_THEME (bookName VARCHAR, themeName VARCHAR);
INSERT INTO BOOK_THEME(bookName, themeName) VALUES ('game of thrones', 'ambition'), ('game of thrones', 'power');

然后是一个表格来记录有关“阅读事件”的信息。每次阅读活动只阅读一本书:

CREATE TABLE READING_EVENT(i SERIAL, venue VARCHAR, bookRead VARCHAR);
ALTER TABLE READING_EVENT ADD PRIMARY KEY (i);
INSERT INTO READING_EVENT(venue, bookRead) VALUES('Municipal Library', 'game of thrones');

这里,棘手的部分来了。我们还有一张表格来记录阅读活动期间积极讨论的主题:

CREATE TABLE READING_EVENT_DISCUSSION(i INTEGER, themeDiscussed VARCHAR);
ALTER TABLE READING_EVENT_DISCUSSION ADD CONSTRAINT constr1 FOREIGN KEY (i) REFERENCES READING_EVENT(i);

现在,我该如何表达themeDiscussed 专栏必须明显引用在那次活动中阅读的书中实际发现的主题之一? bookName 列存在于 READING_EVENT 表中,而不是在我们希望声明 FK 的 READING_EVENT_DISCUSSION 中。

【问题讨论】:

  • 碰巧的是,another question on dba.SE 今天解决了 exact 同样的问题。考虑问题(已经包括对您问题的部分答案)和我的答案。
  • @ErwinBrandstetter 这是一个类似但不一样的情况。我添加了一个显示基本对应关系的示意图,实际上它似乎是相同的模式。然而,有一个关键的区别:在dba.stackexchange.com/q/58970/34332 问题中,pin_inst 表确实是多余的(正如您在回复中指出的那样)。零件实例的引脚实例始终是该零件的所有引脚。就我而言,阅读活动中讨论的主题是书中主题的SUBSET。所以我的理解是只能通过触发器来实现。
  • 我觉得这个问题相当混乱。模式确实是相同的,但该问题的细节尚不清楚(至少对我而言)。看到这个和答案:Many to Many and Weak Entities
  • 转换 Artist -> BookAlbum -> ReadingEventTrack -> BookThemeAlbumTrack -> ReadingEventDiscussion。唯一的区别是您不需要AlbumTrack 中的TrackNo,主键是(那里提到的)(trackID, albumID) -> (ThemeName, i)(在阅读活动中不允许两次主题。)
  • @ypercube 我是正确的。问题的症结在于,在您提供的链接中,在专辑表中,艺术家表的 FK 也是专辑主键的一部分,而在我的情况下,阅读的书不是阅读事件 PK 的一部分,并且在Erwin 的链接部分 id 不是 pin 的 PK 的一部分。但我同意使用您提出的组织确实可以解决问题。

标签: sql postgresql


【解决方案1】:

您省略了书名上的所有外键。

这就是为什么我用一套完整的增强表定义来回答,这是关于外键的,对吧?舒尔你举了一个精简的例子。

要解决的问题是reading_event_discussion 中的记录必须与该书中存在的主题有关:

drop table book cascade;
drop table book_theme;
drop table reading_event cascade;
drop table reading_event_discussion;

create table book (
    name text primary key -- new, a must because it is FK in reading_event
);
insert into book (name) values ('game of thrones'),('Database design');

create table book_theme (
    bookname  text references book(name), -- new
    themename text
);
insert into book_theme (bookname, themename) values 
  ('game of thrones', 'ambition'), ('game of thrones', 'power');

create table reading_event (
  i        SERIAL primary key, 
  venue    text, 
  bookread text references book(name) -- FK is new
);
insert into reading_event (venue, bookRead) VALUES
  ('Municipal Library', 'game of thrones');  

-- this is the solution: extended reference check
create or replace function themecheck (i integer, th text) returns boolean as $$
    select 
     (th in (select themename from book_theme bt 
       join reading_event re on i=re.i and re.bookRead=bt.bookname))
$$ language sql;

create table reading_event_discussion (
    i integer references reading_event(i), 
    themeDiscussed text check (themecheck (i, themeDiscussed))
);

-- Test statements:
-- just check data
select * from reading_event;
-- this should be ok
insert into reading_event_discussion values (1,'ambition'),(1,'power');
-- this must be refused
insert into reading_event_discussion values (1,'databases');

所以解决办法是写一个自定义的检查函数。这不能移植到其他数据库系统。

可以用多种语言(plpgsql、pltcl、...)编写此函数,但 SQL 函数可以内联到查询中并且可能更快。

【讨论】:

    猜你喜欢
    • 2021-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-30
    • 1970-01-01
    • 2019-01-14
    相关资源
    最近更新 更多