sqlite3 外鍵作用原理和使用方法
來源 https://blog.csdn.net/devanchen/article/details/66530098
從SQLite 3.6.19 開始支持 外鍵約束. 外鍵約束用來強制 兩個表之間”存在”的關系.
不論update,insert,replace,要涉及到外鍵的改變的,要確保父表的存在,否則會因為約束的存在,導致操作失敗.
1. sqlite3 默認為關閉外鍵限制的,如要打開,請執行如下命令:
PRAGMA FOREIGN_KEYS=ON;
- sqlite3 外鍵的創建和使用方法一:
例如,我們創建如下兩個表,Persons 和 Orders。
create table persons ( id_p integer not null, lastname varchar(20), firstname varchar(20), address varchar(100), city varchar(100), primary key(id_p) ); create table orders ( id_o integer not null, orderno not null, id_p integer, primary key(id_o), foreign key(id_p) references persons(id_p) );
假如在 persons 已插入如下數據:
id_p lastname firstname address city
1 Adams John Oxford Street London 2 Bush George Fifth Avenue New York 3 Carter Thomas Changan Street Beijing
外鍵插入限制:
當執行如下命令往 orders 插入記錄時:
insert into orders values(1, 22456, 4);
發生錯誤 Error: foreign key constraint failed
由於 persons 中沒有 id_p 為4記錄而導致插入失敗。
外鍵刪除限制:
假設 orders 已插入如下數據:
id_o orderno id_p
1 22456 1
當執行如下命令從 persons 刪除記錄時:
delete from persons where id_p=1;
發生錯誤 Error: foreign key constraint failed
由於在 orders 中有記錄其 id_p=1 導致不能直接從 persons 中刪除,必需首先刪除 orders 中所有 id_p=1 的記錄,才能從 persons 中刪除 id_p=1 的記錄。
執行 drop table persons; 刪除表格時,也必需確保 orders 中所有相關的記錄全部刪除時,刪除表格才能成功。
外鍵更新限制:
假設 orders 已插入如下數據:
id_o orderno id_p
1 22456 1
當執行如下命令更新 persons 的記錄時:
update persons set id_p=4 where id_p=1;
發生錯誤 Error: foreign key constraint failed
因為 orders 存在 id_p=1 的記錄,所有不能在 persons 中直接更新 id_p=1 的記錄,必需從 orders 中刪除所有 id_p=1 的記錄才能在 persons 中任相應的更新。
- sqlite3 外鍵的創建和使用方法二:
如果想從 persons 中刪除或更新相應的記錄后,會在 orders 中有相應的體現而不是提示錯誤。則可按如下方式創建 orders。
create table persons ( id_p integer not null, lastname varchar(20), firstname varchar(20), address varchar(100), city varchar(100), primary key(id_p) ); create table orders ( id_o integer not null, orderno integer not null, id_p integer, primary key(id_o), foreign key(id_p) references persons(id_p) on delete cascade on update cascade );
外鍵插入限制:
與方法一保持一致。
外鍵刪除限制:
假設 persons 中已插入方法一中的數據。且 orders 中插入了如下數據:
id_o orderno id_p
1 22456 1
當執行如下命令從 persons 中刪除記錄時:
delete from persons where id_p=1;
會成功刪除 id_p=1 的記錄,且連帶着 orders 中所有 id_p=1 的記錄會自動被刪除。
執行 drop table persons; 刪除表格時,會成功刪除 persons,連帶着 orders 中所有相關的記錄也會被刪除。
外鍵更新限制:
假設 persons 中已插入方法一中的數據。且 orders 中插入了如下數據:
id_o orderno id_p
1 22456 1
2 22457 1
當執行如下命令更新 persons 中的記錄時:
update persons set id_p=4 where id_p=1;
會成功更新 id_p=1 的記錄,且 orders 中的記錄會自動更改為如下所示:
id_o orderno id_p
1 22456 4
2 22457 4
==========================
SQLite 外鍵支持
來源 https://blog.csdn.net/woshinia/article/details/41679709
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行為不會執行
==================== End