本文件說明 SQLite 版本 3.6.19 (2009-10-14) 中引入的 SQL 外來鍵約束支援。
第一節透過範例介紹 SQL 外來鍵的概念,並定義本文件其餘部分使用的術語。第二節說明應用程式必須採取哪些步驟才能在 SQLite 中啟用外來鍵約束(預設為停用)。下一節,也就是第三節,說明使用者必須建立哪些索引才能使用外來鍵約束,以及哪些索引應該建立才能讓外來鍵約束有效率地運作。第四節說明 SQLite 支援的進階外來鍵相關功能,第五節說明 ALTER 和 DROP TABLE 指令如何增強以支援外來鍵約束。最後,第六節列舉目前實作中遺漏的功能和限制。
此文件不包含用於在 SQLite 中建立外來鍵約束的語法的完整說明。這可以在 CREATE TABLE 陳述的說明文件中找到。
SQL 外來鍵約束用於在資料表之間強制執行「存在」關係。例如,考慮使用下列 SQL 指令建立的資料庫架構
CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER -- Must map to an artist.artistid! );
使用此資料庫的應用程式有權假設在 track 資料表中的每一列都有一個對應的列在 artist 資料表中。畢竟,宣告中的註解是這麼說的。不幸的是,如果使用者使用外部工具編輯資料庫,或者應用程式中有錯誤,可能會在 track 資料表中插入與 artist 資料表中的任何列都不對應的列。或者,可能會從 artist 資料表中刪除列,在 track 資料表中留下孤立的列,而這些列與 artist 中剩下的列都不對應。這可能會導致應用程式稍後發生故障,或者至少讓應用程式的編碼更困難。
一個解決方案是將 SQL 外來鍵約束新增到資料庫架構中,以強制執行 artist 和 track 資料表之間的關係。為此,可以透過修改 track 資料表的宣告為下列內容來新增外來鍵定義
CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER, FOREIGN KEY(trackartist) REFERENCES artist(artistid) );
如此一來,限制是由 SQLite 執行的。嘗試插入不對應於 artist 表格中任何一列的列到 track 表格中會失敗,而當 track 表格中存在相依列時,嘗試從 artist 表格中刪除一列也會失敗。有一個例外:如果 track 表格中的外來鍵欄位為 NULL,則不需要 artist 表格中有對應的項目。以 SQL 表示,這表示對於 track 表格中的每一列,下列運算式會評估為 true
trackartist IS NULL OR EXISTS(SELECT 1 FROM artist WHERE artistid=trackartist)
提示:如果應用程式需要 artist 和 track 之間更嚴格的關聯,其中不允許在 trackartist 欄位中使用 NULL 值,只要在架構中加入適當的「NOT NULL」限制即可。
還有其他幾種方法可以將等效的外來鍵宣告新增到 CREATE TABLE 陳述式中。請參閱 CREATE TABLE 文件 以取得詳細資料。
下列 SQLite 命令列工作階段說明了新增到 track 表格的外來鍵限制的效果
sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 1 Dean Martin 2 Frank Sinatra sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 11 That's Amore 1 12 Christmas Blues 1 13 My Way 2 sqlite> -- This fails because the value inserted into the trackartist column (3) sqlite> -- does not correspond to row in the artist table. sqlite> INSERT INTO track VALUES(14, 'Mr. Bojangles', 3); SQL error: foreign key constraint failed sqlite> -- This succeeds because a NULL is inserted into trackartist. A sqlite> -- corresponding row in the artist table is not required in this case. sqlite> INSERT INTO track VALUES(14, 'Mr. Bojangles', NULL); sqlite> -- Trying to modify the trackartist field of the record after it has sqlite> -- been inserted does not work either, since the new value of trackartist (3) sqlite> -- Still does not correspond to any row in the artist table. sqlite> UPDATE track SET trackartist = 3 WHERE trackname = 'Mr. Bojangles'; SQL error: foreign key constraint failed sqlite> -- Insert the required row into the artist table. It is then possible to sqlite> -- update the inserted row to set trackartist to 3 (since a corresponding sqlite> -- row in the artist table now exists). sqlite> INSERT INTO artist VALUES(3, 'Sammy Davis Jr.'); sqlite> UPDATE track SET trackartist = 3 WHERE trackname = 'Mr. Bojangles'; sqlite> -- Now that "Sammy Davis Jr." (artistid = 3) has been added to the database, sqlite> -- it is possible to INSERT new tracks using this artist without violating sqlite> -- the foreign key constraint: sqlite> INSERT INTO track VALUES(15, 'Boogie Woogie', 3);
正如您所預期的,不可能透過刪除或更新 artist 表格中的列來操縱資料庫到違反外來鍵限制的狀態
sqlite> -- Attempting to delete the artist record for "Frank Sinatra" fails, since sqlite> -- the track table contains a row that refer to it. sqlite> DELETE FROM artist WHERE artistname = 'Frank Sinatra'; SQL error: foreign key constraint failed sqlite> -- Delete all the records from the track table that refer to the artist sqlite> -- "Frank Sinatra". Only then is it possible to delete the artist. sqlite> DELETE FROM track WHERE trackname = 'My Way'; sqlite> DELETE FROM artist WHERE artistname = 'Frank Sinatra'; sqlite> -- Try to update the artistid of a row in the artist table while there sqlite> -- exists records in the track table that refer to it. sqlite> UPDATE artist SET artistid=4 WHERE artistname = 'Dean Martin'; SQL error: foreign key constraint failed sqlite> -- Once all the records that refer to a row in the artist table have sqlite> -- been deleted, it is possible to modify the artistid of the row. sqlite> DELETE FROM track WHERE trackname IN('That''s Amore', 'Christmas Blues'); sqlite> UPDATE artist SET artistid=4 WHERE artistname = 'Dean Martin';
父表格是指外來鍵限制所參考的表格。本節範例中的父表格是 artist 表格。有些書籍和文章將其稱為被參考表格,這可以說是比較正確,但容易造成混淆。
子表格是指套用外來鍵限制的表格,以及包含 REFERENCES 子句的表格。本節範例使用 track 表格作為子表格。其他書籍和文章將其稱為參考表格。
父鍵是指外來鍵限制所參考的父表格中的欄位或欄位組。這通常(但並非總是)是父表格的主鍵。父鍵必須是父表格中已命名的欄位或欄位組,而不是 rowid。
子鍵是子表中受外鍵約束約束的欄位或欄位組,且包含 REFERENCES 子句。
如果子表中任一列子鍵欄位為 NULL,或在父表中存在一列,其中每個父鍵欄位包含等於其關聯子鍵欄位中值的數值,則外鍵約束會成立。
在上述段落中,「等於」一詞表示使用在此指定的規則比較值時相等。下列說明適用
若要在 SQLite 中使用外鍵約束,必須編譯函式庫,且未定義SQLITE_OMIT_FOREIGN_KEY或SQLITE_OMIT_TRIGGER。如果定義了 SQLITE_OMIT_TRIGGER,但未定義 SQLITE_OMIT_FOREIGN_KEY,則 SQLite 的行為與版本 3.6.19 (2009-10-14)之前相同 - 會分析外鍵定義,且可以使用PRAGMA foreign_key_list查詢,但不會強制執行外鍵約束。在此組態中,PRAGMA foreign_keys指令為空操作。如果定義了 OMIT_FOREIGN_KEY,則無法分析外鍵定義(嘗試指定外鍵定義會產生語法錯誤)。
假設函式庫已編譯並啟用外鍵約束,仍必須在執行階段使用 PRAGMA foreign_keys 指令由應用程式啟用。例如
sqlite> PRAGMA foreign_keys = ON;
外鍵約束預設為停用(為了向後相容性),因此必須為每個 資料庫連線 分別啟用。(不過請注意,SQLite 的未來版本可能會變更,讓外鍵約束預設為啟用。謹慎的開發人員不會對外鍵是否預設啟用做任何假設,而是會視需要啟用或停用。)應用程式也可以使用 PRAGMA foreign_keys 陳述式來判斷外鍵是否目前已啟用。下列命令列會話示範此功能
sqlite> PRAGMA foreign_keys; 0 sqlite> PRAGMA foreign_keys = ON; sqlite> PRAGMA foreign_keys; 1 sqlite> PRAGMA foreign_keys = OFF; sqlite> PRAGMA foreign_keys; 0
提示:如果「PRAGMA foreign_keys」指令傳回沒有資料,而不是包含「0」或「1」的單一列,則表示您使用的 SQLite 版本不支援外鍵(可能是因為它早於 3.6.19,或是因為它是在定義 SQLITE_OMIT_FOREIGN_KEY 或 SQLITE_OMIT_TRIGGER 的情況下編譯的)。
無法在 多重陳述式交易(當 SQLite 不在 自動提交模式 時)中啟用或停用外鍵約束。嘗試這麼做不會傳回錯誤;它只是沒有作用。
通常,外來金鑰約束的父金鑰是父表格的主金鑰。如果它們不是主金鑰,則父金鑰欄位必須共同受 UNIQUE 約束約束或具有 UNIQUE 索引。如果父金鑰欄位具有 UNIQUE 索引,則該索引必須使用在父表格的 CREATE TABLE 陳述式中指定的排序順序。例如,
CREATE TABLE parent(a PRIMARY KEY, b UNIQUE, c, d, e, f); CREATE UNIQUE INDEX i1 ON parent(c, d); CREATE INDEX i2 ON parent(e); CREATE UNIQUE INDEX i3 ON parent(f COLLATE nocase); CREATE TABLE child1(f, g REFERENCES parent(a)); -- Ok CREATE TABLE child2(h, i REFERENCES parent(b)); -- Ok CREATE TABLE child3(j, k, FOREIGN KEY(j, k) REFERENCES parent(c, d)); -- Ok CREATE TABLE child4(l, m REFERENCES parent(e)); -- Error! CREATE TABLE child5(n, o REFERENCES parent(f)); -- Error! CREATE TABLE child6(p, q, FOREIGN KEY(p, q) REFERENCES parent(b, c)); -- Error! CREATE TABLE child7(r REFERENCES parent(c)); -- Error!
作為表格 child1、child2 和 child3 一部分而建立的外來金鑰約束都很好。作為表格 child4 一部分而宣告的外來金鑰是一個錯誤,因為即使父金鑰欄位已編製索引,索引也不是 UNIQUE。表格 child5 的外來金鑰是一個錯誤,因為即使父金鑰欄位具有唯一索引,索引也使用不同的排序順序。表格 child6 和 child7 不正確,因為雖然它們在父金鑰上都有 UNIQUE 索引,但金鑰與單一 UNIQUE 索引的欄位並不完全相符。
如果資料庫架構包含需要檢視多個表格定義才能識別的外來金鑰錯誤,則在建立表格時不會偵測到這些錯誤。反之,此類錯誤會阻止應用程式準備以使用外來金鑰的方式修改子表格或父表格內容的 SQL 陳述式。在變更內容時報告的錯誤是「DML 錯誤」,而在變更架構時報告的錯誤是「DDL 錯誤」。因此,換句話說,需要檢視子表格和父表格的錯誤設定外來金鑰約束是 DML 錯誤。外來金鑰 DML 錯誤的英文錯誤訊息通常是「外來金鑰不符」,但如果父表格不存在,也可能是「沒有此表格」。如果發生下列情況,則會報告外來金鑰 DML 錯誤
上述最後一項要點由下列說明:
CREATE TABLE parent2(a, b, PRIMARY KEY(a,b)); CREATE TABLE child8(x, y, FOREIGN KEY(x,y) REFERENCES parent2); -- Ok CREATE TABLE child9(x REFERENCES parent2); -- Error! CREATE TABLE child10(x,y,z, FOREIGN KEY(x,y,z) REFERENCES parent2); -- Error!
相反地,如果只要查看子表格的定義,而不用查閱父表格的定義,就能辨識出外來鍵錯誤,則子表格的 CREATE TABLE 陳述式會失敗。由於錯誤發生在架構變更期間,因此這是 DDL 錯誤。外來鍵 DDL 錯誤會回報,無論在建立表格時是否已啟用外來鍵約束。
子鍵欄位不需要索引,但幾乎總是會有幫助。回到 第 1 節 中的範例,每次應用程式從 artist 表格(父表格)中刪除一列時,會執行等同於下列 SELECT 陳述式,以搜尋 track 表格(子表格)中的參照列。
SELECT rowid FROM track WHERE trackartist = ?
其中上述的 ? 會以從 artist 表格中刪除的記錄的 artistid 欄位值取代(請記住,trackartist 欄位是子鍵,而 artistid 欄位是父鍵)。或者更一般地說
SELECT rowid FROM <child-table> WHERE <child-key> = :parent_key_value
如果這個 SELECT 回傳任何列,SQLite 會得出刪除父資料表的列會違反外來金鑰約束,並回傳錯誤。如果父金鑰的內容被修改,或新列被插入父資料表,可能會執行類似的查詢。如果這些查詢無法使用索引,它們會被迫對整個子資料表進行線性掃描。在非平凡的資料庫中,這可能會非常昂貴。
因此,在大多數實際系統中,應該在每個外來金鑰約束的子金鑰欄位上建立索引。子金鑰索引不必是(而且通常不會是)UNIQUE 索引。回到第 1 節中的範例,外來金鑰約束的有效實作的完整資料庫架構可能是
CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER REFERENCES artist ); CREATE INDEX trackindex ON track(trackartist);
上面的區塊使用簡寫形式來建立外來金鑰約束。將「REFERENCES <parent-table>」子句附加到欄位定義會建立外來金鑰約束,將欄位對應到 <parent-table> 的主金鑰。請參閱 CREATE TABLE 文件以取得更多詳細資訊。
複合外來金鑰約束是指子金鑰和父金鑰都是複合金鑰。例如,考慮以下資料庫架構
CREATE TABLE album( albumartist TEXT, albumname TEXT, albumcover BINARY, PRIMARY KEY(albumartist, albumname) ); CREATE TABLE song( songid INTEGER, songartist TEXT, songalbum TEXT, songname TEXT, FOREIGN KEY(songartist, songalbum) REFERENCES album(albumartist, albumname) );
在此系統中,song 資料表中的每個項目都必須對應到 album 資料表中具有相同歌手和專輯組合的項目。
父金鑰和子金鑰必須具有相同的基數。在 SQLite 中,如果任何子金鑰欄位(在本例中為 songartist 和 songalbum)為 NULL,則不需要在父資料表中對應的列。
SQLite 中的每個外來金鑰約束都分類為立即或延遲。外來金鑰約束預設為立即。到目前為止提出的所有外來金鑰範例都是立即外來金鑰約束。
如果一個陳述式修改資料庫內容,導致在陳述式結束時立即違反外來金鑰約束,則會擲回例外,且陳述式的效果會被還原。相反地,如果一個陳述式修改資料庫內容,導致違反延遲外來金鑰約束,則不會立即報告違反。延遲外來金鑰約束不會在交易嘗試 COMMIT 之前檢查。只要使用者有一個開啟的交易,資料庫就能存在於違反任何數量的延遲外來金鑰約束的狀態。然而,只要外來金鑰約束仍然違反,COMMIT 就會失敗。
如果目前的陳述式不在一個明確的交易中(一個 BEGIN/COMMIT/ROLLBACK 區塊),則會在陳述式執行完畢後立即提交一個隱含交易。在這種情況下,延遲約束的行為與立即約束相同。
要將一個外來金鑰約束標記為延遲,其宣告必須包含下列子句
DEFERRABLE INITIALLY DEFERRED -- A deferred foreign key constraint
指定外來金鑰約束的完整語法可用於 CREATE TABLE 文件的一部分。將上述片語替換為下列任何一個,會建立一個立即外來金鑰約束。
NOT DEFERRABLE INITIALLY DEFERRED -- An immediate foreign key constraint NOT DEFERRABLE INITIALLY IMMEDIATE -- An immediate foreign key constraint NOT DEFERRABLE -- An immediate foreign key constraint DEFERRABLE INITIALLY IMMEDIATE -- An immediate foreign key constraint DEFERRABLE -- An immediate foreign key constraint
defer_foreign_keys pragma 可用於暫時將所有外來金鑰約束變更為延遲,不論它們是如何宣告的。
下列範例說明使用延遲外來金鑰約束的效果。
-- Database schema. Both tables are initially empty. CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER REFERENCES artist(artistid) DEFERRABLE INITIALLY DEFERRED ); sqlite3> -- If the foreign key constraint were immediate, this INSERT would sqlite3> -- cause an error (since as there is no row in table artist with sqlite3> -- artistid=5). But as the constraint is deferred and there is an sqlite3> -- open transaction, no error occurs. sqlite3> BEGIN; sqlite3> INSERT INTO track VALUES(1, 'White Christmas', 5); sqlite3> -- The following COMMIT fails, as the database is in a state that sqlite3> -- does not satisfy the deferred foreign key constraint. The sqlite3> -- transaction remains open. sqlite3> COMMIT; SQL error: foreign key constraint failed sqlite3> -- After inserting a row into the artist table with artistid=5, the sqlite3> -- deferred foreign key constraint is satisfied. It is then possible sqlite3> -- to commit the transaction without error. sqlite3> INSERT INTO artist VALUES(5, 'Bing Crosby'); sqlite3> COMMIT;
當資料庫處於不滿足遞延外來金鑰約束的狀態時,巢狀儲存點交易可能會被釋放。另一方面,交易儲存點(當前沒有開啟交易時開啟的非巢狀儲存點)會受到與 COMMIT 相同的限制,如果在資料庫處於這種狀態時嘗試釋放它,則會失敗。
如果 COMMIT 陳述式(或交易 SAVEPOINT 的 RELEASE)失敗,因為資料庫目前處於違反遞延外來金鑰約束的狀態,且目前有巢狀儲存點,則巢狀儲存點仍保持開啟狀態。
外來金鑰 ON DELETE 和 ON UPDATE 子句用於設定在從父資料表中刪除列(ON DELETE)或修改現有列的父金鑰值(ON UPDATE)時執行的動作。單一外來金鑰約束可以為 ON DELETE 和 ON UPDATE 設定不同的動作。外來金鑰動作在許多方面類似於觸發器。
與 SQLite 資料庫中的每個外來金鑰關聯的 ON DELETE 和 ON UPDATE 動作為「NO ACTION」、「RESTRICT」、「SET NULL」、「SET DEFAULT」或「CASCADE」之一。如果未明確指定動作,則預設為「NO ACTION」。
NO ACTION:設定「NO ACTION」表示:當父金鑰從資料庫中修改或刪除時,不會執行任何特殊動作。
限制:當存在一個或多個子鍵對應到父鍵時,「限制」動作表示禁止應用程式刪除(對於 ON DELETE RESTRICT)或修改(對於 ON UPDATE RESTRICT)父鍵。限制動作與一般外來鍵約束執行的不同之處在於,限制動作處理會在欄位更新後立即發生,而不是像立即約束一樣在目前陳述結束時發生,或像遞延約束一樣在目前交易結束時發生。即使它所附加的外來鍵約束是遞延的,設定限制動作會導致 SQLite 在刪除或修改具有依賴子鍵的父鍵時立即傳回錯誤。
設為 NULL:如果設定的動作為「設為 NULL」,則當父鍵被刪除(對於 ON DELETE SET NULL)或修改(對於 ON UPDATE SET NULL)時,子表中所有對應到父鍵的列之子鍵欄位會被設定為包含 SQL NULL 值。
設為預設值:「設為預設值」動作類似於「設為 NULL」,但每個子鍵欄位會被設定為包含欄位的預設值,而不是 NULL。請參閱 CREATE TABLE 文件,以了解如何將預設值指定給表格欄位。
串聯:「串聯」動作會將父鍵的刪除或更新操作傳播到每個依賴的子鍵。對於「ON DELETE CASCADE」動作,這表示子表中與已刪除父列相關聯的每一列也會被刪除。對於「ON UPDATE CASCADE」動作,這表示儲存在每個依賴子鍵中的值會被修改,以符合新的父鍵值。
例如,在以下所示的外來鍵中加入「ON UPDATE CASCADE」子句,會增強第 1 節中的範例架構,允許使用者更新 artistid(外來鍵約束的父鍵)欄位,而不會破壞參考完整性
-- Database schema CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER REFERENCES artist(artistid) ON UPDATE CASCADE ); sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 1 Dean Martin 2 Frank Sinatra sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 11 That's Amore 1 12 Christmas Blues 1 13 My Way 2 sqlite> -- Update the artistid column of the artist record for "Dean Martin". sqlite> -- Normally, this would raise a constraint, as it would orphan the two sqlite> -- dependent records in the track table. However, the ON UPDATE CASCADE clause sqlite> -- attached to the foreign key definition causes the update to "cascade" sqlite> -- to the child table, preventing the foreign key constraint violation. sqlite> UPDATE artist SET artistid = 100 WHERE artistname = 'Dean Martin'; sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 2 Frank Sinatra 100 Dean Martin sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 11 That's Amore 100 12 Christmas Blues 100 13 My Way 2
設定 ON UPDATE 或 ON DELETE 動作並不表示外來鍵約束不需要滿足。例如,如果設定「ON DELETE SET DEFAULT」動作,但父資料表中沒有與子鍵欄位的預設值相符的列,則在依賴的子鍵存在時刪除父鍵時,仍會造成外來鍵違規。例如
-- Database schema CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER DEFAULT 0 REFERENCES artist(artistid) ON DELETE SET DEFAULT ); sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 3 Sammy Davis Jr. sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 14 Mr. Bojangles 3 sqlite> -- Deleting the row from the parent table causes the child key sqlite> -- value of the dependent row to be set to integer value 0. However, this sqlite> -- value does not correspond to any row in the parent table. Therefore sqlite> -- the foreign key constraint is violated and an is exception thrown. sqlite> DELETE FROM artist WHERE artistname = 'Sammy Davis Jr.'; SQL error: foreign key constraint failed sqlite> -- This time, the value 0 does correspond to a parent table row. And sqlite> -- so the DELETE statement does not violate the foreign key constraint sqlite> -- and no exception is thrown. sqlite> INSERT INTO artist VALUES(0, 'Unknown Artist'); sqlite> DELETE FROM artist WHERE artistname = 'Sammy Davis Jr.'; sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 0 Unknown Artist sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 14 Mr. Bojangles 0
熟悉 SQLite 觸發器 的人會注意到,上述範例中示範的「ON DELETE SET DEFAULT」動作與以下 AFTER DELETE 觸發器效果類似
CREATE TRIGGER on_delete_set_default AFTER DELETE ON artist BEGIN UPDATE child SET trackartist = 0 WHERE trackartist = old.artistid; END;
每當外來鍵約束的父資料表中的列被刪除,或儲存在父鍵欄位或欄位中的值被修改時,事件的邏輯順序為
ON UPDATE 外來鍵動作與 SQL 觸發器之間有一個重要的差異。只有在父鍵的值被修改,使得新的父鍵值不等於舊值時,才會執行 ON UPDATE 動作。例如
-- Database schema CREATE TABLE parent(x PRIMARY KEY); CREATE TABLE child(y REFERENCES parent ON UPDATE SET NULL); sqlite> SELECT * FROM parent; x ---- key sqlite> SELECT * FROM child; y ---- key sqlite> -- Since the following UPDATE statement does not actually modify sqlite> -- the parent key value, the ON UPDATE action is not performed and sqlite> -- the child key value is not set to NULL. sqlite> UPDATE parent SET x = 'key'; sqlite> SELECT IFNULL(y, 'null') FROM child; y ---- key sqlite> -- This time, since the UPDATE statement does modify the parent key sqlite> -- value, the ON UPDATE action is performed and the child key is set sqlite> -- to NULL. sqlite> UPDATE parent SET x = 'key2'; sqlite> SELECT IFNULL(y, 'null') FROM child; y ---- null
本節說明 CREATE TABLE、ALTER TABLE 和 DROP TABLE 指令與 SQLite 外來鍵互動的方式。
CREATE TABLE 指令無論是否 啟用外來鍵約束 都會執行相同的操作。建立資料表時,不會檢查外來鍵約束的父鍵定義。沒有任何東西可以阻止使用者建立引用不存在的父資料表或不存在或未集體繫結到 PRIMARY KEY 或 UNIQUE 約束的父鍵欄位的外部鍵定義。
當啟用外來鍵約束時,ALTER TABLE 指令在兩個方面的工作方式不同
無法使用「ALTER TABLE ... ADD COLUMN」語法新增包含 REFERENCES 子句的欄位,除非新欄位的預設值為 NULL。嘗試這麼做會傳回錯誤。
如果使用「ALTER TABLE ... RENAME TO」指令重新命名一個表,而該表是其中一個或多個外來鍵約束的父表,則外來鍵約束的定義會修改為以父表的新的名稱來參照。儲存在 sqlite_schema 表 中的子項 CREATE TABLE 陳述式或陳述式的文字會修改為反映新的父表名稱。
如果在準備時啟用外來鍵約束,DROP TABLE 指令會執行一個隱含的 DELETE,在刪除表之前移除表中的所有列。隱含的 DELETE 不會觸發任何 SQL 觸發器,但可能會呼叫外來鍵動作或約束違規。如果立即外來鍵約束遭到違規,DROP TABLE 陳述式會失敗,且表不會被刪除。如果延遲外來鍵約束遭到違規,則當使用者嘗試提交交易時,如果外來鍵約束違規仍然存在,就會報告錯誤。任何在隱含 DELETE 中遇到的「外來鍵不符」錯誤都會被忽略。
這些對 ALTER TABLE 和 DROP TABLE 命令的增強功能的用意是確保它們無法用於建立包含外來金鑰違規的資料庫,至少在外來金鑰約束式啟用的時候。不過,這個規則有一個例外。如果父金鑰不受父表格定義中建立的 PRIMARY KEY 或 UNIQUE 約束式的約束,但受使用 CREATE INDEX 命令建立的索引的 UNIQUE 約束式約束,則子表格可以在不造成「外來金鑰不符」錯誤的情況下填入資料。如果從資料庫架構中刪除 UNIQUE 索引,則父表格本身會被刪除,不會報告任何錯誤。不過,資料庫可能會處於子表格的外來金鑰約束式包含不參考任何父表格列的列的狀態。如果資料庫架構中的所有父金鑰都受到 PRIMARY KEY 或 UNIQUE 約束式的約束(這些約束式是作為父表格定義的一部分新增的,而不是外部 UNIQUE 索引),則可以避免這種情況。
上面描述的 DROP TABLE 和 ALTER TABLE 命令的屬性僅在啟用外來金鑰時適用。如果使用者認為它們不可取,則解決方法是在執行 DROP 或 ALTER TABLE 命令之前使用 PRAGMA foreign_keys 來停用外來金鑰約束式。當然,在外來金鑰約束式停用的時候,沒有任何東西可以阻止使用者違反外來金鑰約束式,從而建立內部不一致的資料庫。
本節列出一些未在其他地方提到的限制和遺漏的功能。
不支援 MATCH 子句。根據 SQL92,MATCH 子句可以附加到複合外來鍵定義,以修改處理子鍵中出現的 NULL 值的方式。如果指定「MATCH SIMPLE」,則如果子鍵值中有一個或多個為 NULL,則子鍵不需要與父表的任何列對應。如果指定「MATCH FULL」,則如果子鍵值中任何一個為 NULL,則父表中不需要對應的列,但所有子鍵值都必須為 NULL。最後,如果外來鍵約束宣告為「MATCH PARTIAL」,且子鍵值中有一個為 NULL,則父表中必須存在至少一列,其非 NULL 子鍵值與父鍵值相符。
SQLite 會分析 MATCH 子句(也就是說,如果您指定 MATCH 子句,不會回報語法錯誤),但不會強制執行。SQLite 中的所有外來鍵約束都會處理,就像指定了 MATCH SIMPLE 一樣。
不支援在延遲模式和立即模式之間切換約束。許多系統允許使用者在執行階段,在 延遲 模式和立即模式之間切換個別外來鍵約束(例如使用 Oracle「SET CONSTRAINT」指令)。SQLite 不支援此功能。在 SQLite 中,外來鍵約束在建立時會永久標記為延遲或立即。
外來鍵動作的遞迴限制。SQLITE_MAX_TRIGGER_DEPTH 和 SQLITE_LIMIT_TRIGGER_DEPTH 設定會決定觸發程式遞迴的最大允許深度。對於這些限制的目的,外來鍵動作會被視為觸發程式。 PRAGMA recursive_triggers 設定不會影響外來鍵動作的運作。無法停用遞迴外來鍵動作。
外來鍵不能跨越架構邊界。也就是說,在 REFERENCES (X.Y)
中,表格 X
將只會在包含 REFERENCES
子句的架構中解析。