分享至

簡介

外鍵描述關係,而正確性與約束中介紹的實體關係圖 (ERD) 對應了這些外鍵的網路或圖形。在這些範例中,只有少數表格及其之間的關係,但視覺化佈局在確保每個必要關係都已考量時,仍然是一個有用的參考。對於較大的資料庫,ERD 非常寶貴,不容置疑。許多資料庫用戶端都內建了產生圖表的工具,但通常需要手動調整才能使其可讀。

The first steps toward a database schema design for tracking books and patrons in a library system.

存在幾種 ERD 符號。完整的 「烏鴉腳」符號是最古老且最具影響力的符號之一,它為 0(圓圈)、1(破折號)或多個(同名的烏鴉腳,如上所示)記錄定義了符號。每條線代表兩個表格之間的關係,並且在每一端都具有兩個符號,每對符號都建立了該側的最小值和最大值。

這種對細節的關注至少部分是歷史遺留物,源於在工作站上執行資料庫伺服器是聞所未聞的時代,而在現代,很少有 ERD 被如此正式地指定。與此處的圖表一樣,「最多一個」的符號和「零到多個」的符號足以表達要點,並且很少需要在這兩者之間以及再分享 SQL 腳本本身之間進行權衡。

串聯行為

將無效的 author_id 插入 books 並不是違反外鍵約束的唯一方法:對 authors 的變更也可能使 books 中的現有資料失效。回到正確性與約束,未強制執行的外鍵導致唐吉訶德的副本具有虛假的 author_id。如何解決皮耶·梅納德和米格爾·德·塞凡提斯之間的矛盾?

如果可以從 authors 中刪除梅納德的記錄,則所討論的唐吉訶德副本將不再具有有效的 author_id。資料庫拒絕這樣做,因為不允許來自子表格或父表格的違規行為。要擺脫皮耶·梅納德,必須首先處置唐吉訶德,可以刪除它或變更其 author_id

隨著受約束關係的網路越來越大,清理此類依賴記錄變得越來越複雜。刪除作者需要刪除他們的所有 books;刪除圖書館需要相同的操作,加上移除其 patrons -- 以及任何具有指向 bookspatrons 外鍵的表格都必須首先刪除,以免這些外鍵約束也隨之被違反。

針對父表格的 DELETE 通常旨在修剪整個關係樹:一個圖書館及其書籍及其讀者,一舉完成(有時它並非旨在執行此操作,這使得了解您的 CASCADE 非常重要!)。由於外鍵約束具體化了這些關係,使其成為可以操作的物件,因此它們還可以幫助自動回應父表格中的變更。宣告 ON DELETE SET NULL 的約束只會使第一個外鍵值失效,而不會進一步遍歷關係圖。ON DELETE CASCADE 確保對 authorsDELETE 將自動刪除這些作者的 books,並通過任何宣告 books 為父表格的外鍵繼續執行。

有時,自然的primary key值也可能會隨著標準和格式的更新或自然索引鍵不可變的假設被證明是不正確而改變。大多數 RDBMS 都支援針對此意外情況的 ON UPDATE CASCADE 行為。

未來已來,一切都需要被摧毀

即使真實的 librariesauthors 永遠不應該被刪除(僅停用或「軟刪除」),自動和手動測試通常都需要預先準備一個全新的空資料庫,甚至每個單獨的測試都需要。刪除並重新建立資料庫會中斷連線、需要提升的權限,並且是啟動速度最慢的解決方案。

通常的做法是使用「拆解」函式或腳本,該函式或腳本逐個表格刪除先前測試可能已插入資料庫中的任何內容。如果沒有 CASCADE 指令,則必須圍繞關係圖以 拓撲排序順序仔細安排這些刪除,以避免違反外鍵約束。使用 CASCADE,一旦您刪除每個資料庫各種關係圖中心的記錄,拆解幾乎可以自行完成。

索引鍵定位

圖書館和作者都早於他們借出和寫作的書籍的任何有用記錄而存在。這些案例對應於物件導向程式設計中的 「has-a」關係類型,在資料庫設計中,這需要將外鍵儲存在相依表格 books 中。

其他情況則不太清楚。想像一下,有些書籍本身是從外部館藏借給圖書館的,並且它們的原始 出處 是分開追蹤的。所有 books 都應該具有 provenance_id,還是 provenances 表格應該具有 book_id 欄位?

Expanding the libraries schema to begin tracking provenance for individual books.

這兩種解決方案都將達到追蹤出處的目的。但是,在 books.provenance_id 的情況下,無法從書籍的出處追溯到書籍 -- 必須在 books 中搜尋符合的 provenance_id。並且由於大多數書籍沒有特殊的出處,因此大多數 provenance_id 的值將為 NULL

在這種情況下,provenances.book_id 方法顯然更優越。book_id 連結是可追蹤的,欄位的使用效率很高,並且 provenances.book_id 甚至是一個primary key,因為一本書不應該從多個地方來到圖書館。De Haan 和 Koppelaars 會將 provenances 稱為 books特殊化,這是一個表格,它將補充資訊新增到由相同primary key識別的父表格中的記錄。booksprovenances 之間的連線是「一對一」關係,因為任何 book_id 值在任一表格中只能存在一個。

CREATE TABLE provenances (
book_id INT NOT NULL PRIMARY KEY,
collection TEXT NOT NULL
);

嚴格來說,出處包括文物的整個監管鏈,而不僅僅是誰最後擁有它。如果為了我們的目的需要,這會使情況變得有些複雜:在 provenances 中每本書有多個記錄,book_id 不再是primary keyprovenances 表格中的記錄不是(或不僅僅是)由外鍵識別,它不再是特殊化,而是「一對多」關係的「多」方 -- 或者,從另一個方向看,是「多對一」關係。

CREATE TABLE provenances (
book_id INT NOT NULL REFERENCES books (book_id),
-- a numeric index (most recent, second most recent, third,
-- and so on) is not strictly required, since the duration
-- could form part of the primary key. However, a range in
-- the primary key makes certain queries, like "who last
-- held most of our books?", more difficult to formulate.
custody_index INT NOT NULL DEFAULT 1,
collection TEXT NOT NULL,
duration DATERANGE NOT NULL,
PRIMARY KEY (book_id, custody_index),
-- custody of the same book shouldn't overlap; remember
-- that the btree_gist Postgres extension is required!
EXCLUDE USING GIST (
book_id WITH =,
duration WITH &&
)
);

多對多關係

在範例結構描述的其他地方,patrons 具有 library_id 值。這表達了一個非常重要 -- 而且可能非常錯誤 -- 的假設:任何人都只會光顧一個圖書館。如果有人去另一家圖書館,他們將不得不重新輸入他們的所有資訊。這違反了另一個重要假設,即 patrons 中的單一記錄對應於一個人。兩者不能同時成立。

類似的解決方案存在第二個問題:我們尚未追蹤誰借出了書。單一讀者可以借閱許多書籍,而單一書籍可以被借閱多次。在結構上,這幾乎與單一圖書館有多位讀者的情況相同,這些讀者本身可能會從多個圖書館借閱書籍。

Adding junction tables to the model allows the relationships between patrons and books, and patrons and libraries, to be fully represented.

「多對多」關係必須在專用表格中表示,通常稱為交集橋接表格,在 其他名稱中。交集表格維護指向其調解的每個表格的外鍵,使其成為其與這些表格關係的「多」方。跨每個外鍵的primary key可防止相同關係的重複。

library_patrons 是交集表格的教科書範例,如下所示

CREATE TABLE library_patrons (
library_id INT NOT NULL REFERENCES libraries (library_id),
patron_id INT NOT NULL REFERENCES patrons (patron_id),
PRIMARY KEY (library_id, patron_id)
);

您可能已經注意到,checkouts 沒有遵循與 library_patrons 相同的命名慣例 -- 它不是 patron_books 或反之亦然。那是因為它 不僅僅是一個交集表格。與 library_patrons 類似,checkouts 維護指向其在多對多關係中連線的表格的外鍵,但它也必須包含有關每個讀者-書籍連線的資訊:借出日期、到期或歸還日期、是否已授予延期。某人多次借閱同一本書也是完全有可能的,因此 (patron_id, book_id) 不是可行的primary key。

建構區塊

多對多關係只是兩種主要關係類型的一種可能的組合。它們非常常見,以至於圖表通常完全省略 library_patrons 樣式的交集表格,而傾向於用「多」符號表示兩端。但是,表格之間連線的每個網路,無論多麼複雜,都可以簡化為其組成的一對一和一對多關係。

邊界

單一資料庫可能(且通常會)包含多個外鍵關係網路。但是,反過來通常是不成立的。MySQL 和 MariaDB 是唯一將結構描述和資料庫混淆的流行關聯式資料庫,因此,只要兩個資料庫都託管在同一伺服器上,就允許跨資料庫外鍵。其他資料庫則不允許。

稍後我們將回到在資料庫和資料庫內的結構描述中組織表格,但考慮多表格關係圖作為資料庫佈局的不可分割的單元非常有用,就像給定概念的組合屬性形成表格佈局的基礎一樣。

關於作者
Dian Fay

Dian Fay

Dian 並沒有真正計畫從大學輟學來專門研究 SQL 和後端開發,但事情就是這樣發生的。十五年後,她設計的資料庫支援從工業物流和追溯系統到百萬級用戶的社群媒體遊戲等各種應用。她是 MassiveJS 的現任維護者,MassiveJS 是一個適用於 Node.js 的開放原始碼資料對應器,專注於充分利用 PostgreSQL。