概要
SQLiteでは既存のテーブルに外部キー制約を追加するのが少し面倒。 いくつか方法があるので、内容によって使い分けるといい。
既存テーブルに外部キーを設定するパターンは 大まかには以下の3つ。
- カラムを新規追加して外部キーを設定する。
- 既存のカラムに外部キーを設定する。
- 既存のカラムのデータ型を変更して外部キーを設定する。
ここでは2.の実行例を記載する。
詳細
OracleやMySQLであれば「alter table ~ add foreign key ~」のように alter table 構文で既存テーブルに外部キー制約を追加することができるが、 SQLiteではこれができない。(ver 3.34.1時点)
SQLiteのテーブルの構成を変更する方法として、 テーブルを作り直してデータを移行する方法 があるが、既存のカラムに外部キーを設定するだけであれば もう少し簡単な方法がある。
簡単に説明すると SQLiteではテーブル構成がスキーマ文(DDL)で保存されており、中身のデータとは別々に管理されている。 データ型を変更するような場合はスキーマ文とデータの両方に手を加える必要があるが、 外部キー制約の追加だけであればスキーマ文のみを変更するだけでいい、ということらしい。
公式(英語) に手順が記載されている。
- トランザクションを開始。
- Pragma schema_versionでスキーマのバージョンを確認する。
- Pragma writable_schema=onにする。
- sqlite_schemaテーブルに保存されているスキーマ文を更新する。
-
他のテーブルに影響が出る場合はそちらにも変更を加える。
(外部キー制約を追加するだけであれば不要。) - Pragma schema_version=Xでスキーマのバージョンを+1する。
- Pragma writable_schema=offにする。
- Pragma Integrity_checkを実行してデータベースが壊れていないか確認する。
- トランザクションをコミットする。
実行例
以下は「receipt」テーブルの「product_id」に外部キー制約を設定する例。
外部キー制約を設定するにはカラムの後に、referencesと参照するテーブル・カラムを追加する。
以下の構文を追記する
references table_name(columu_name)
実行環境
- Windows 10 64bit
- SQLite3 (3.34.1) Command-Line Shell
実行手順
- トランザクションを開始
- スキーマのバージョンを確認
- スキーマを編集可能にする
- 現在のスキーマを確認する
-
スキーマテーブルを更新してproduct_idに外部キー制約を追加する。
スキーマ文は「sqlite_schema」というテーブルに登録されているため、 これをupdateしてやればいい。 - 「.schema」コマンドでスキーマが変更されたことを確認する
- スキーマのバージョンを一つ上げる
- スキーマを編集不能に戻す
- データベースが壊れていないかチェックする
- トランザクションをコミットする
- 正しく外部キー制約が追加されたか確認する
sqlite> CREATE TABLE product (
...> product_id number primary key
...> , product_name text
...> , price number
...> );
sqlite> CREATE TABLE receipt (
...> row_num number
...> , create_date text
...> , product_id number
...> , quantity text
...> );
sqlite> --# 1.
sqlite> begin transaction;
sqlite> --# 2.
sqlite> pragma schema_version;
2
sqlite> --# 3.
sqlite> pragma writable_schema=on;
sqlite> --# 4.
sqlite> .schema receipt
CREATE TABLE receipt (
row_num number
, create_date text
, product_id number
, quantity text
);
sqlite> --# 5.
sqlite> update
...> sqlite_schema
...> set
...> sql=
...> 'create table receipt (
...> row_num number
...> , create_date text
...> , product_id number references product(product_id)
...> , quantity text
...> )'
...> where
...> tbl_name = 'receipt'
...> ;
sqlite> --# 6.
sqlite> .schema receipt
create table receipt (
row_num number
, create_date text
, product_id number references product(product_id)
, quantity text
);
sqlite> --# 7.
sqlite> pragma schema_version=3;
sqlite> --# 8.
sqlite> pragma writable_schema=off;
sqlite> --# 9.
sqlite> pragma Integrity_check;
ok
sqlite> --# 10.
sqlite> commit;
sqlite> --# 11.
sqlite> pragma foreign_keys=on;
sqlite> insert into receipt values (
...> 5
...> , '2021-02-21'
...> , 99999
...> , 100
...> );
Error: FOREIGN KEY constraint failed
既存のデータはどうなるのか
既存のデータが外部キー制約違反になっていてもエラーにはならない。
きちんとやるなら「Pragma foreign_keys=on」で外部キー制約を有効ににした後、
外部キー制約違反にならないようデータを修正する。
どのデータが外部キー制約違反かわからない場合は「pragma foreign_key_check」で確認できる。
参考URL
-
SQLite Foreign Key Support
SQLite 公式の外部キーに関するドキュメント(英語)
https://www.sqlite.org/foreignkeys.html -
ALTER TABLE
SQLite 公式のテーブル変更に関するドキュメント(英語)
https://www.sqlite.org/lang_altertable.html
-
あさはか備忘録: SQLite 3 外部キー制約のあるテーブルを作成する
https://sfnovicenotes.blogspot.com/2020/01/sqlite3.html -
あさはか備忘録: SQLite 3 既存のカラムから外部キー制約を削除する
https://sfnovicenotes.blogspot.com/2021/03/sqlite-3.html -
あさはか備忘録: SQLite 3 テーブルのスキーマ文を編集する
https://sfnovicenotes.blogspot.com/2021/01/sqlite-3.html