2014-02-15 29 views
3

在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

碰巧,[關於dba.SE的另一個問題](http://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away)今天解決了*確切*相同的問題。考慮這個問題(已經包括你問題的答案的一部分)和我的答案。 –

+0

@ErwinBrandstetter它是一個類似但不一樣的情況。我添加了一個示意圖,顯示了基本的對應關係,事實上看起來它是相同的模式。但是有一個關鍵的區別:在http://dba.stackexchange.com/q/58970/34332問題中,'pin_inst'表是真正多餘的(正如你在那裏的迴應中指出的那樣)。零件實例的引腳實例始終是該零件的所有引腳。就我而言,閱讀活動中討論的主題是書中主題的* SUBSET *。所以我的理解是,只有通過觸發器才能做到這一點。 –

+0

我覺得這個問題相當混亂。模式的確是一樣的,但細節在這個問題上並不清楚(至少對我來說)。看到這個和答案:** [多對多和弱實體](http://dba.stackexchange。com/questions/34040/many-to-many-and-weak-entities/34050#34050)** –

回答

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函數可以內聯到查詢中,並且可能會更快。