明細の取引先IDが取引先マスターに存在しないまま保存されると、後から照合できなくなります。画面の選択リスト以外にも登録経路があるなら、保存先でも関連を確認する方法を持たせられます。
SQLiteの外部キーは、子テーブルの値が親テーブルのキーを参照する関係を定義します。定義に加えて、接続ごとの検査の有効化も確認します。以前の別の接続で有効だったことを、そのまま現在の接続の根拠にはしません。
接続直後に設定して読み戻す
次は取引先C1と、それを参照する明細を持つ架空DBです。トランザクション開始前の設定を分かりやすくするため、Python 3.12以降のautocommit=Trueを指定します。複数変更をまとめる処理の例ではありません。
import sqlite3
db = sqlite3.connect(":memory:", autocommit=True)
try:
db.execute("PRAGMA foreign_keys=ON")
if db.execute("PRAGMA foreign_keys").fetchone() != (1,):
raise RuntimeError("外部キーを有効にできません")
db.execute("CREATE TABLE clients(id TEXT PRIMARY KEY NOT NULL)")
db.execute(
"CREATE TABLE tickets(id TEXT PRIMARY KEY NOT NULL,"
"client_id TEXT NOT NULL REFERENCES clients(id))"
)
db.execute("INSERT INTO clients VALUES (?)", ("C1",))
try:
db.execute("INSERT INTO tickets VALUES (?,?)", ("T0", "C404"))
except sqlite3.IntegrityError:
print("存在しない取引先を拒否")
else:
raise AssertionError("不正な参照を通過しました")
db.execute("INSERT INTO tickets VALUES (?,?)", ("T1", "C1"))
rows = db.execute("SELECT id,client_id FROM tickets").fetchall()
violations = db.execute("PRAGMA foreign_key_check").fetchall()
print("保存した明細:", rows)
print("参照違反:", violations)
assert rows == [("T1", "C1")] and violations == []
finally:
db.close()
C404を参照する明細は拒否され、T1とC1の組だけが残ります。参照違反の一覧は空です。Windows・Python 3.12.10・SQLite 3.49.1で、この結果を確認しました。実台帳へ制約を追加した例ではありません。
トランザクション中に切り替えない
外部キーの切替は、トランザクションの途中では効果がありません。すでにトランザクションが始まる設定を使う場合は、接続時の管理方法と有効化する順序を合わせて確認してください。
ONを実行したつもりでも、現在値を読み戻して1になっているかを確認する形にします。ツール内で新しく接続を作る箇所が複数あるなら、そのたびに同じ方針を適用する必要があります。
NULLと削除の条件も決める
外部キーだけでは参照する列のNULLを必ず拒否するわけではありません。この例は取引先が必須なのでclient_idにNOT NULLも付けています。未割当を許す業務なら、別の定義になります。
親を削除したときに子を残すのか、削除を止めるのか、連動して消すのかも決めます。ON DELETE CASCADEを便利な既定値として付けると、想定より広くデータが消える場合があります。実際の関連と残す必要のある記録から選んでください。
既存の台帳を見直すときは、現在の定義と参照違反を読み取るところから始めます。親がないからといって、明細を勝手に削除したり、適当な取引先へ付け替えたりする処理にはしません。
接続と基本検索はSQLite入門、台帳の入力や状態の整理は在庫・台帳の自動化設計で補えます。
参考資料
SQLite外部キーとPython sqlite3の公式資料を2026年11月1日に確認しました。
