SQLite外鍵(Foreign Key)支持

轉自:http://y150988451.iteye.com/blog/952584

SQLite外鍵(Foreign Key)支持

從SQLite 3.6.19 開始支持 外鍵約束. (Ubuntu 10.04 的SQLite版本是 3.6.22,  Debian 6.0 的SQLite版本是  3.7.0)

外鍵約束用來強制 兩個表之間”存在”的關係. 比如, 考慮下面的SQL命令建立的schema

CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
CREATE TABLE track(
trackid INTEGER,
trackname TEXT,
trackartist INTEGER — 必須映射到一個 artist.artistid 記錄
);
使用這個數據庫的應用可以假定: 對於在track表裏每一行, 都在artist表都存在一個對應的行.   不幸的是, 如果用戶使用外部工具編輯數據庫, 或者在應用程序中存在一個bug. 那麼可能在track表中插入一行,而在artist表中沒有相應的記錄. 或者, 在artist表中刪除某些行,  就會在track表裏留下孤兒行(orphaned rows),  它們在artist表中剩下的記錄找到任何對應的行.  這可能在以後會導致應用的功能出錯. 或者至少讓編寫應用程序更復雜.

一個解決方法就是, 爲數據庫添加一個外鍵約束,在artist和track這兩個表之間強制實施一個約束. 增加外鍵定義的track表的定義如下:
CREATE TABLE track(
trackid     INTEGER,
trackname   TEXT,
trackartist INTEGER,
FOREIGN KEY(trackartist) REFERENCES artist(artistid)
);

這樣,外鍵約束就由SQLite強制實施.  往 track表插入一行 在 artist表中沒有對應的數據的記錄的企圖註定是要失敗的,o(∩∩)o

如果在track表還存在依賴於artist中的某行的記錄, 那麼嘗試從 artist表刪除該行, 也會失敗.

也就是說, 對於在track表中的每一行,,下面的表達式都是真:

trackartist IS NULL OR EXISTS(SELECT 1 FROM artist WHERE artistid=trackartist)

在SQLite中啓用外鍵支持

1)爲了在SQLite中使用外鍵約束, 編譯sqlite庫時, 不能使用 忽略 外鍵和觸發器, 也就是 SQLITE_OMIT_FOREIGN_KEY 和 SQLITE_OMIT_TRIGGER不能被定義

2)必須在運行時打開, 因爲  默認是關閉的

PRAGMA foreign_keys = ON;

要求和建議的數據庫索引

通常, 外鍵約束的父鍵在父表裏是主鍵. 如果它們不是主鍵, 那麼父鍵欄 必須受一個UNIQUE約束 或者 有一個 UNIQUE 索引.

如果數據庫schema還有外鍵錯誤, 就需要查看多個表才能找到錯誤. 數據表創建時不會檢測這些錯誤,

這些錯誤會阻止應用程序 用SQL語句來修改子表或者父表的內容. 當內容被改變時,報告”DML errors”;當schema被改變時報告”DDL errors”

也就是說,錯誤地配置外鍵約束,要求檢查子表和父表的是DML錯誤, 一般顯示”foreign key mismatch” 或者 “no such table”

SELECT rowid FROM <child-table> WHERE <child-key> = :parent_key_value
如果這個SELECT返回數據, 那麼SQLite就斷定, 從父表刪除某行, 將會違背外鍵約束,並返回錯誤. 如果父鍵的值被修改或者在父表插入新的一行,也會產生類似的查詢.

如果這些查詢沒有使用索引, 它們將強迫對整個子表做線性 查找(scan), 這代價可太大了

在大多數實際系統中,  應該在子鍵這一欄建立索引. 子鍵的索引不必(並且是通常都不必) 有一個UNIQUE 索引(因爲在子表中的多行對應於父表中的一行)

CREATE INDEX trackindex ON track(trackartist);

ON DELETE 和 ON UPDATE行爲

外鍵的ON DELETE和 ON  UPDATE從句, 可以用來配置  當從父表中刪除 某些行時發生的行爲(ON DELETE).  或者 修改存在的行的父鍵的值, 發生的行爲(ON UPDATE)

單個外鍵約束可以爲ON DELETE和ON UPDATE配置不同的行爲.   外鍵行爲在很多時候類似於 觸發器(trigger)

ON DELETE和ON UPDATE的行爲是 NO ACTION,  RESTRICT, SET NULL,  SET DEFAULT 或者 CASCADE

如果沒有明確指定星聞,那麼默認就是NO ACTION

NO ACTION: 當父鍵被修改或者刪除時, 沒有特別的行爲發生

RESTRICT:  存在一個或者多個子鍵對應於相應的父鍵時,  應用程序禁止刪除(ON DELETE RESTRICT)或者修改(ON UPDATE RESTRICT) 父鍵

RESTRICT與普通的外鍵約束的區別是,  當字段(field)更新時, RESTRICT行爲立即發生

SET NULL: 父鍵被刪除(ON DELETE SET NULL) 或者修改 (ON UPDATE SET NULL)

SET DEFAULT: 類似於SET NULL

CASCADE: 將實施在父鍵上的刪除或者更新操作,傳播給與之關聯的子鍵.

對於 ON DELETE CASCADE, 同被刪除的父表中的行 相關聯的子表中的每1行,也會被刪除.

對於ON UPDATE CASCADE,  存儲在子表中的每1行,對應的字段的值會被自動修改成同新的父鍵匹配

舉例:

CREATE TABLE artist(

artistid    INTEGER PRIMARY KEY,

artistname  TEXT

);

CREATE TABLE track(

trackid     INTEGER,

trackname   TEXT,

trackartist INTEGER REFERENCES artist(artistid) ON UPDATE CASCADE

);

INSERT INTO artist VALUES(1, ‘Dean Martin’);

INSERT INTO artist VALUES(2, ‘Frank Sinatra’);

INSERT INTO track VALUES(14, ‘Mr. Bojangles’, 2);

INSERT INTO track VALUES(15, “That’s Amore”, 2);

INSERT INTO track VALUES(12, ‘Christmas Blues’, 1);

INSERT INTO track VALUES(13, ‘My Way’, 2);

sqlite> PRAGMA foreign_keys = ON;

(默認是關閉的, 要在運行時打開)

sqlite> SELECT * FROM artist;

1|Dean Martin

2|Frank Sinatra

sqlite> SELECT * FROM track;

14|Mr. Bojangles|2

15|That’s Amore|2

12|Christmas Blues|1

13|My Way|2

sqlite> UPDATE artist SET artistid = 999 WHERE artistname = ‘Dean Martin’;

(爲 Dean Martin更改 artist表中的artistid欄目.

一般情況下, 這將 產生一個約束, 因爲會讓 track表中的 一條記錄成爲孤兒記錄

但 對外鍵定義使用了ON UPDATE CASCADE從句後,  會把這個更新傳給 子表, 從而讓外鍵約束不被打破)

sqlite> SELECT * FROM artist;

2|Frank Sinatra

999|Dean Martin

sqlite> SELECT * FROM track;
14|Mr. Bojangles|2
15|That’s Amore|2
12|Christmas Blues|999
13|My Way|2
配置一個ON UPDATE或者ON DELETE行爲並不意味着 外鍵約束 並不必要滿足.
舉例來說, 如果 配置了 ON DELETE SET DEFAULT 行爲, 如果在父表中沒有 與子表欄目中默認值 相對應 的 行記錄, 當依賴的子鍵存在於子表中是, 刪除父鍵, 會破壞外鍵.
舉例:
sqlite> PRAGMA foreign_keys = ON;
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
);
INSERT INTO artist VALUES(3, ‘Sammy Davis Jr.’);
INSERT INTO track VALUES(14, ‘Mr. Bojangles’, 3);
sqlite> DELETE FROM artist WHERE artistname = ‘Sammy Davis Jr.’;
Error: foreign key constraint failed
從父表中刪除一行, 會引起子表中相關的子鍵被設置成整數0
然而, 這個值不對應與 附表中的任何一行數據. 所以,外鍵約束被打破, 就拋出了異常
sqlite> INSERT INTO artist VALUES(0, ‘Unknown Artist’);
往父表中添加一行, 其主鍵爲0
這樣刪除記錄就不會打破外鍵約束了
sqlite> DELETE FROM artist WHERE artistname = ‘Sammy Davis Jr.’;

sqlite> SELECT * FROM artist;
0|Unknown Artist
sqlite> SELECT * FROM track;
14|Mr. Bojangles|0
這寫都很類似於 SQLite 觸發器(triggers), 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;
外鍵約束的父表中的某行被刪除, 或者存儲在父鍵中的值 被修改時: 時間的邏輯順序是:
1. 執行在BEFORE 觸發器程序
2. 檢查本地(非外鍵)約束
3. 在父表中 更新或者刪除行
4. 執行要求的外鍵行爲
5. 執行 AFTER觸發器 程序

在ON UPDATE外鍵行爲 和 SQL 觸發器之間一個重要區別就是, ON UPDATE 行爲只有在 父鍵的值 被修改並且父鍵的值修改得跟原來不一樣時,才執行.
如果下UPDATE SET 語句修改的值,跟原來一樣, ON UPDATE行爲不會執行


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章