Claude Codeをに実装をお任せした時にハマったことです。
集計定義に列を1つ足して再生成スクリプトを流した。なのに新しい列がテーブルに出てこない。INSERT すると今度は no such column で落ちる——。原因は CREATE TABLE IF NOT EXISTS の「IF NOT EXISTS」でした。
Claude Codeが自己解決してくれずこちらが原因を特定して修正箇所を指定することで修正してくれました。
結論(先に解決策)
CREATE TABLE IF NOT EXISTS は既存テーブルがあると何もしません。列を追加したいなら、対象テーブルを DROP してから作り直す(洗い替え)か、ALTER TABLE ADD COLUMN でマイグレーションします。
-- 公開用テーブルを作り直すなら
DROP TABLE IF EXISTS api_basic_population;
-- → このあと再生成スクリプトを流すと、新スキーマで CREATE される
# 例: 対象テーブルを DROP してから再生成
python scripts/analyze.py --target basic_demographics
症状
「分析定義(YAML)に列を1つ追加 → 再生成スクリプトを実行」しても、
- 新しい列がテーブルに増えない
- INSERT が
no such column: <新しい列>で失敗する
sqlite3.OperationalError: table api_basic_population has no column named new_metric
スクリプトはエラーも警告も出さずに「成功」して見えることがあり、なおさら気づきにくいです。
原因
再生成処理がテーブルをこう作っていました。
def publish(conn, table, columns, rows):
conn.execute(f"CREATE TABLE IF NOT EXISTS {table} ({columns})")
conn.execute(f"DELETE FROM {table}") # 中身は消す
conn.executemany(f"INSERT INTO {table} ...", rows) # 入れ直す
ポイントは CREATE TABLE IF NOT EXISTS です。
- 初回: テーブルが無いので、新スキーマ(新列あり)で作られる
-
2回目以降: テーブルが既にあるので、
IF NOT EXISTSにより CREATE 文がまるごとスキップされる
つまり「列定義を変えても、既存テーブルがある限りスキーマは古いまま」。中身を DELETE → INSERT で入れ替えるロジックだけが動くので、新列を含む INSERT が no such column で弾かれる、という流れです。
CREATE TABLE IF NOT EXISTS はスキーマ変更を一切検知しません。あるのは「テーブルが存在するか」だけです。
解決
洗い替え(毎回作り直す設計なら一番シンプル)
公開用テーブルのように「ソースから毎回再生成する」ものは、IF NOT EXISTS に頼らず DROP → CREATE が確実です。
def publish(conn, table, columns, rows):
conn.execute(f"DROP TABLE IF EXISTS {table}") # 先に落とす
conn.execute(f"CREATE TABLE {table} ({columns})") # 新スキーマで作る
conn.executemany(f"INSERT INTO {table} ...", rows)
これで列定義を変えるたびに、最新スキーマで作り直されます。スクリプト引数で対象を絞れるなら、その1テーブルだけ DROP して再生成すれば十分です。
既存データを残したいなら ALTER TABLE
履歴を消したくない(洗い替えできない)テーブルは、列追加を明示します。
ALTER TABLE api_basic_population ADD COLUMN new_metric REAL;
SQLite の ADD COLUMN は既存行に対して NULL(または DEFAULT 値)で列を足すだけなので軽量です。
どちらを選ぶか
| 状況 | 対応 |
|---|---|
| ソースから毎回再生成する公開用テーブル | DROP → CREATE(洗い替え) |
| 蓄積データで履歴を残したい | ALTER TABLE ADD COLUMN |
「CREATE TABLE IF NOT EXISTS を置いておけばスキーマも追従してくれる」という思い込みが地雷です。IF NOT EXISTS は存在チェックだけで、差分マイグレーションはしてくれない——これを覚えておけば次から悩みません。
まとめ
-
CREATE TABLE IF NOT EXISTSは既存テーブルがあると CREATE 文を丸ごとスキップする - だから列を足しても反映されず、INSERT が
no such columnで落ちる - 再生成系は DROP → CREATE で洗い替え、蓄積系は ALTER TABLE ADD COLUMN
これは e-Stat(政府統計)の人口データを SQLite で公開する API のパイプラインで踏みました。公開用 DB を「読み取り専用・書き込みは再生成スクリプトのみ」に絞る設計の話は、別途まとめる予定です。