外键约束,其中一些列值驻留在其他表中

时间:2014-02-15 00:09:31

标签: sql postgresql

在PostgreSQL中表达外键约束的正确/惯用方法是什么,其中部分FK列驻留在另一个表中?

我将用一个例子来说明这一点(省略一些明显的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

1 个答案:

答案 0 :(得分: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函数可以内联到查询中,可能更快。