2021年2月11日木曜日

SQLite3 新しいカラムを追加して外部キー制約を設定する

概要

SQLiteでは既存のテーブルに外部キー制約を追加するのが少し面倒 (ver 3.34.1時点)。 いくつか方法があるので、内容によって使い分けるといい。

既存テーブルに外部キーを設定する方法は 大まかには以下の3パターン。

  1. カラムを新規追加して外部キーを設定する。
  2. 既存のカラムに外部キーを設定する。
  3. 既存のカラムのデータ型を変更して外部キーを設定する。

ここでは1.の実行例を記載する。

実行例

「receipt」テーブルに「product_id」を追加し、 外部キー制約を設定する。

7Vtdc9o4FP01nmkftoNt7DiPhdB0pmS3m2Qn3aeMsIXRVFiuEAnsr98rW/IH4jNAcBpIhpGupCv7nqMjjSQstzueXXOUjm5YhKnltKKZ5V5ZjnPhB/AtDfPc0A6c3BBzEuUmuzTckf+wMraUdUoiPKlVFIxRQdK6MWRJgkNRsyHO2XO92pDReq8pirFhuAsRNa0PJBIjZbVbrbLgKybxSHUdeKpggMKfMWfTRPVnOe4w++TFY6R9qfqTEYrYc8Xk9iy3yxkTeWo862IqQ6vDlrf7sqK0eG6OE7FNg+Dh7+tfw39u+J8X47+8h/t7fN3/w23nbp4QnaqApJxFUwi00/pg9Tzr0rMCP0u4VmB/VO8i5jp+8FqpTAo0kKbORCAuFMxuCwwAnEAkwRwMdpanFKUTklXPLSNCoz6as6nQjnSuMyQzHN3mKMu6AHgfnMmsdD4E53fqYWQxoiROIB1CUGSPHY4n8Cx9NBGFA0AMR6p9AUr+pGMS6gLB2c+CEbbuKn8v24X8SIypKsordxllPAuK62cfKKFogGmnYIqukrAEZy9HaaXV0JN/qquKvX0l/8BuQq5Y8IS5wLOKSVHgGrMxFnwOVVRpW7FxXs8+V6gfKNuownrbVUakhltceC46u4XhiZIYsCh681v17grflf4ulnS32BuigGaCBO7IKE6qPIdE5T1LU8b+XUaCZ4yElVwHBARBtHzhRVZLBkWcpfeIx1goQ8qIJGXvCdAr+FthgCKFYKkqpHio2w6YEGysKawCVTjNouR14B/i1m198iwPHrcLebvMe1dZdQ7MSoCwMCSlDwwj4xlPxBKCL7DQyT7L2F6UmPxcLzqbWTuv82ETTdextEaYndnhG+z4/m03fmTTFyr5sTP0rQXoq9on8wziOaSZmo1IFOFkM6aFshiYrtGc9cNnM6YVEN1XxfBi1Vz3CO0OhiUEL0I4GIY7A6onr7xuZ5KikCRxP2/pvxBxc24KAzwY7jTL7Iv4bPUo9l6VAcGb0PhFXrwPjfdPrvGXe7Lj4AqPI6L92S8T+AMCGmwN6KkEXi9mlwh8gsb4xGBuVPcXqXkB475qvj28TVHztm3AfVbzpqj55anVvO2c1XyLsdNgNXeXqDkJzzJ+IFwbI+PLFuU+1foG6VimOQ4xScUHq+dagWt97mYJx+pcKEunqxJB8FE7gAeq+lhJnfe8eblqE3L1lsARtyedoL5huPX+pH8senr7ziO/3SrjeOK1/Rq0KfuCnjlJvb1VxtEALcZOc1cZnnkABir9mEzH53XGYZBtyjrDexsHPO9TyE+++eeZBzxnITfHToOF3DzdCTlGAj9G8HUW88Og2xgxP5/kNFfMT7735/0OJznHG+7NP8nRy4EKgL+mKBFEzPcD8gSyvYQ7rwdgU/Tat8/y3BR5tp1T67Nv7ql9OfVtqiYptN/80xl/2elMftYekcNBecTLVCe9PLUDwo2RcHOnzMAZRzHWJxo4m65vMUWCsKRXltSiLEdL5TACJ9Fneakfsr1bGMb37AYlcxNOPCPih24D6X8l6p88lbua6UEtM3OdSSAMP6qZSiuZLZtlOd1u60PYzQchEzblIV4TZH2pTui5bhOBZMS3FYjiNw21kxNt5BlST7j2wGuue3+Xk2Xl5EbPKmqWMYiYv7tqVXLRcOQuODKunuexMRxtvggO2fLnFnn18ictbu9/" style="background-color: rgb(255, 255, 255);" id="svg1145" sodipodi:docname="foreign_key_create.svg" inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"> image/svg+xml product (商品) product (商品) PK product_id product_name price receipt(レシート) receipt(レシート) row_num create_date quantity FK product_id Viewer does not support full SVG 1.1
実行環境
  • Windows 10 64bit
  • SQLite3 (3.34.1) Command-Line Shell

カラムの新規追加と外部キー制約の設定は「alter table~」構文を使って まとめて処理できる。


alter table
    receipt
add column 
    product_id number references product(product_id);                    
                    

実際の作業は以下のようになる。

  1. トランザクションを開始
  2. 既存テーブルのスキーマを確認
  3. 外部キー制約を無効化
  4. カラムを追加して外部キー制約を設定
  5. 設定後のスキーマを確認する
  6. 外部キー制約を有効化
  7. トランザクションのコミット

sqlite> -- # 1.
sqlite> begin transaction;

sqlite> -- # 2.
sqlite> .schema product
CREATE TABLE product (
    product_id     number primary key
    , product_name text
    , price        number
);
sqlite> .schema receipt
CREATE TABLE receipt (
    row_num       number
    , create_date text
    , quantity
);

sqlite> -- # 3.
sqlite> pragma foreign_keys=off;

sqlite> -- # 4.
sqlite> alter table
   ...>     receipt
   ...> add column
   ...>     product_id number references product(product_id);

sqlite> -- # 5.
sqlite> .schema receipt
CREATE TABLE receipt (
    row_num       number
    , create_date text
    , quantity
, product_id number references product(product_id));

sqlite> -- # 6.
sqlite> pragma foreign_keys=on;

sqlite> -- # 7.
sqlite> commit;
                    

「.schema」コマンドの結果が崩れてしまって気になるという場合は以下を参照のこと。
SQLite 3 テーブルのスキーマ文を編集する

既存のデータはどうなるのか

カラムの追加時にdefault制約を追加出来るため、 指定すればデフォルト値が、指定しなければnullが入る。
ただし、外部キー制約が有効(pragma foreign_keys=on)の状態では デフォルト値をnull以外にすることはできない。

カラム追加時にデフォルト値を指定する場合は以下の通り。


sqlite> .mode table
sqlite> select * from receipt;
+---------+-------------+----------+
| row_num | create_date | quantity |
+---------+-------------+----------+
| 101     | 2019-12-01  | 5        |
| 102     | 2019-12-02  | 5        |
| 103     | 2019-12-03  | 8        |
+---------+-------------+----------+
sqlite> alter table
   ...>     receipt
   ...> add column
   ...>     product_id number references product(product_id) default 0;
sqlite> select * from receipt;
+---------+-------------+----------+------------+
| row_num | create_date | quantity | product_id |
+---------+-------------+----------+------------+
| 101     | 2019-12-01  | 5        | 0          |
| 102     | 2019-12-02  | 5        | 0          |
| 103     | 2019-12-03  | 8        | 0          |
+---------+-------------+----------+------------+
                    

デフォルト値を指定しなかった場合は以下の通り。


sqlite> .mode table
sqlite> .nullval (null)
sqlite> begin transaction;
sqlite> select * from receipt;
+---------+-------------+----------+
| row_num | create_date | quantity |
+---------+-------------+----------+
| 101     | 2019-12-01  | 5        |
| 102     | 2019-12-02  | 5        |
| 103     | 2019-12-03  | 8        |
+---------+-------------+----------+
sqlite> alter table
   ...>     receipt
   ...> add column
   ...>     product_id number references product(product_id);
sqlite> select * from receipt;
+---------+-------------+----------+------------+
| row_num | create_date | quantity | product_id |
+---------+-------------+----------+------------+
| 101     | 2019-12-01  | 5        | (null)     |
| 102     | 2019-12-02  | 5        | (null)     |
| 103     | 2019-12-03  | 8        | (null)     |
+---------+-------------+----------+------------+
                    

pragma foreign_keys=on のまま、カラムを追加して外部キーと null以外のデフォルト値を設定しようするとエラーになる。


sqlite> pragma foreign_keys=on;
sqlite> alter table
   ...>     receipt
   ...> add column
   ...>     product_id number references product(product_id) default 0;
Error: Cannot add a REFERENCES column with non-NULL default value
                    
注意

既存のデータが外部キー制約違反になっていてもエラーにはならない。
きちんとやるなら「foreign_keys=on」で外部キー制約を有効ににした後、 外部キー制約違反にならないようデータをupdateする。
どのデータが外部キー制約違反かわからない場合は「pragma foreign_key_check」で確認できる。

参考URL