SQLite3構文 alter(add column)スキーマ再定義(テーブル再作成による定義変更)
alter
文ではできないことがある。新テーブル生成+データ移行+旧テーブル削除すればいい。
成果物
情報源
概要
前回まででalter
文にはできないことがあることがわかった。たとえばprimary key
制約をもった列を追加することはできない。というか、テーブル変更できるパターンのほうが遥かに少ない。
でも、それらの縛りを無視して変更したいときもある。そこで今回の方法を使う。
方法
- 外部キー制約が有効になっている場合、
PRAGMA foreign_keys = OFF
を使用して無効化 - トランザクションを開始する
- テーブル
X
に関連付けられているすべてのインデックス、トリガー、ビューを網羅する:SELECT type, sql FROM sqlite_master WHERE tbl_name='X'.
- 新しいテーブル
new_X
を作る:create table new_X(...);
- データをコピーする:
insert into new_X select ... from X;
- 旧テーブル
X
を削除する:drop table X;
new_X
の名前をX
に変更する:alter table new_X rename to X
- 3で見つけたインデックス、トリガー、ビューを変更する(テーブルと同様に
create
文で) - ビューがスキーマXの影響を受ける方法でテーブルXを参照している場合、
drop view
を使用してそれらのビューを削除し、create view
を使用してスキーマの変更に対応するために必要な変更を加えて再作成する - 外部キー制約が最初に有効化されていた場合、
PRAGMA foreign_key_check
を実行して、スキーマの変更が外部キー制約を壊していないことを確認する - 2で開始したトランザクションを
commit
する - 1で外部キー制約が有効になっていた場合は有効化する
後で確かめてわかったが、上記の方法は順序がまちがってないか? 正しくは以下。rename
は作り直し終えた後にやるのがポイント。どちらも公式に書いてあった。それとも私の理解がアレなせいか、翻訳のせいなのか。頼むから動作するSQL文を書いてくれ。
- 新しいテーブルを作成
- データをコピーする
- 古いテーブルをドロップ
- 新しい名前を古い名前に変更
さらに公式には明記していないが、たぶん以下が正しいのでは?
- 新しいテーブルを作成
- データをコピーする
- インデックス、トリガー、ビューを削除&再作成
- 古いテーブルをドロップ
- 新しい名前を古い名前に変更
やってみる
ターミナルで以下コマンド実行
sqlite3
元テーブルを作成する。
pragma foreign_keys = ON; create table X( A text, B text, C text ); insert into X values('1','B0','C0'); create table Y(A integer primary key);
変更してみる。
pragma foreign_keys = OFF; begin transaction; create table new_X( A integer primary key, C text unique default NULL, D text check(0<=D and D<=100), E text not null, F text default (datetime('now','localtime')), G integer references Y(A) ); insert into new_X(A, C, D, E, G) select cast(A as int), C, 0, 'E', NULL from X; drop table X; alter table new_X rename to X; commit; pragma foreign_keys = ON;
select sql from sqlite_master;
CREATE TABLE Y(A integer primary key) CREATE TABLE "X"( A integer primary key, C text unique default NULL, D text check(0<=D and D<=100), E text not null, F text default (datetime('now','localtime')), G integer references Y(A) )
.headers on .mode column select * from X;
A|C|D|E|F|G 1|C0|0|E|2019-08-26 13:41:27|
インデックス、トリガー、ビュー有り
ターミナルで以下コマンド実行
sqlite3
元テーブルを作成する。
pragma foreign_keys = ON; create table X( A text, B text, C text ); insert into X values('1','B0','C0'); create table Y(A integer primary key); insert into Y values(1); create table trriger_log(id integer primary key, log text); create index idx_A on X(A); create view view_all as select * from X; create trigger trg_1 insert on X begin insert into trriger_log(log) values('トリガーで挿入!'); end;
まずはインデックスの存在確認。
.indices X
idx_A
ビューの確認。
select * from view_all;
1|B0|C0
トリガーの確認。
select * from trriger_log;
insert into X values(2, 'B1', 'C1');
select * from trriger_log;
1|トリガーで挿入!
変更
pragma foreign_keys = OFF; begin transaction; create table new_X( A integer primary key, C text unique default NULL, D text check(0<=D and D<=100), E text not null, F text default (datetime('now','localtime')), G integer references Y(A) ); insert into new_X(A, C, D, E, G) select cast(A as int), C, 0, 'E', NULL from X; drop view view_all; drop index idx_A; drop trriger trg_1; drop table X; create index idx_A on new_X(A); create view view_all as select * from new_X; create trigger trg_1 insert on new_X begin insert into trriger_log(log) values('トリガーで挿入!'); end; alter table new_X rename to X; commit; pragma foreign_keys = ON;
長文のほうの方法にあったのとは一部順番が違う。rename
する前にデータ移行とインデックス等の削除&再作成している。内容が合っているかもわからん。そもそもインデックス、ビュー、トリガーについて未学習ゆえよく知らん。
テーブル確認。
.tables
X Y trriger_log view_all
インデックス。sqlite_autoindex_X_1
って何? 勝手にできたの? いつ? どこで? なぜ?
.indices X
idx_A sqlite_autoindex_X_1
トリガーは動いた。
insert into X(A, C, D, E, G) values(3, NULL, 0, '', 1);
select * from trriger_log;
1|トリガーで挿入!
最後にsqlite_master
を確認。
select * from sqlite_master;
table|Y|Y|3|CREATE TABLE Y(A integer primary key) table|trriger_log|trriger_log|4|CREATE TABLE trriger_log(id integer primary key, log text) table|X|X|6|CREATE TABLE "X"( A integer primary key, C text unique default NULL, D text check(0<=D and D<=100), E text not null, F text default (datetime('now','localtime')), G integer references Y(A) ) index|sqlite_autoindex_X_1|X|7| index|idx_A|X|2|CREATE INDEX idx_A on "X"(A) view|view_all|view_all|0|CREATE VIEW view_all as select * from "X" trigger|trg_1|X|0|CREATE TRIGGER trg_1 insert on "X" begin insert into trriger_log(log) values('トリガーで挿入!'); end
sqlite_autoindex_X_1
ってsql
入ってないんだが。ゴミじゃないの? どうしよう処理に困る……。
失敗ログ
失敗1
pragma foreign_keys = OFF; begin transaction; create table new_X( A integer primary key, C text unique default NULL, D text check(0<=D and D<=100), E text not null, F text default (datetime('now','localtime')), G integer references Y(A) ); insert into new_X(A, C, D, E, G) select cast(A as int), C, 0, 'E', NULL from X; drop view view_all; drop table X; create view view_all as select * from new_X; alter table new_X rename to X; commit; pragma foreign_keys = ON;
.tables
X Y trriger_log view_all
インデックスは、名前が変わってる?
.indices X
sqlite_autoindex_X_1
トリガーも死んでる。
insert into X(A, C, D, E, G) values(3, NULL, 0, '', 1);
select * from trriger_log;
失敗2
pragma foreign_keys = OFF; begin transaction; create table new_X( A integer primary key, C text unique default NULL, D text check(0<=D and D<=100), E text not null, F text default (datetime('now','localtime')), G integer references Y(A) ); insert into new_X(A, C, D, E, G) select cast(A as int), C, 0, 'E', NULL from X; drop table X; alter table new_X rename to X; commit; pragma foreign_keys = ON;
rename
した後にエラー発生して中断された。
Error: error in view view_all: no such table: main.X
おそらく*
で全列を取得しているから。修正によって列が追加・削除・変更されたので作り直す必要があるのだろう。
失敗3
方法のとおり、rename
後にdrop view
, create view
してみる。
pragma foreign_keys = OFF; begin transaction; create table new_X( A integer primary key, C text unique default NULL, D text check(0<=D and D<=100), E text not null, F text default (datetime('now','localtime')), G integer references Y(A) ); insert into new_X(A, C, D, E, G) select cast(A as int), C, 0, 'E', NULL from X; drop table X; alter table new_X rename to X; drop view view_all; create view view_all as select * from X; commit; pragma foreign_keys = ON;
やはりrename
後にエラー。
Error: error in view view_all: no such table: main.X
というか、commit
してないのになぜエラーになる?
失敗4
pragma foreign_keys = OFF; begin transaction; create table new_X( A integer primary key, C text unique default NULL, D text check(0<=D and D<=100), E text not null, F text default (datetime('now','localtime')), G integer references Y(A) ); insert into new_X(A, C, D, E, G) select cast(A as int), C, 0, 'E', NULL from X; drop view view_all; drop table X; create view view_all as select * from new_X; alter table new_X rename to X; commit; pragma foreign_keys = ON;
やはりrename
後にエラー。
Error: error in view view_all: no such table: main.X
というか、commit
してないのになぜエラーになる?
new_X
がそのまま残っている。
.tables
Y new_X trriger_log view_all
コマンドを一括コピペしたからエラー後にコミットしてしまったようだ。
気持ち的にはエラー時点で中断されるつもりだった。が、1行ずつが個別のコマンドになっていると思われる。begin
〜commit
をメソッドのような単位だと思っていたら勘違いする。
所感
面倒すぎ
とてつもなく面倒くさい。RDBMSってスキーマを変更するとデータ移行などがものすごく大変な印象がある。特に制約が絡んでくると頭までこんがらがってくる。その上SQLite3はalter
コマンドが貧弱なせいで、今回のような作り直しが必要になる……。
さらに
だが、なんと公式にはさらに面倒なことが書いてある。じつはSQLite3でスキーマ再定義しようとすると以下の3パターンに分けられる。
alter add column
: (以下2
,3
以外)- 今回のパターン: 列の削除・順序変更。
UNIQUE
・PK
の追加/削除。CHECK
/FOREIGN KEY
/NOT NULL
の追加。型の変更 - 前回のパターン:
CHECK
、FOREIGN KEY
、NOT NULL
の削除。列のDEFAULT
値の追加・削除・変更
条件が複雑すぎ! 3
は「ディスク上のコンテンツにまったく影響を与えない一部の変更」ということらしいが、よく意味がわからない。私にはこれらを適時適切に使い分けられる自信がない。そもそも条件を覚えられない。
たのむから「どういう内容に変更するか?」という本質的な問題に集中させてくれ。「どうやって」でこんなに悩みたくない。
感想
3
の前回パターンはsqlite_master
を変更する方法。これを確認すべきかと思ったが……もうやだ。やめる。使い分けられる自信がない。条件を覚えられない。
2
ができれば3
の場合でもスキーマ変更できるから問題ない。3
のほうがデータ移行せずに済むという大きなメリットはあるけど。
あとさ、パッと見、トリガー、ビュー、インデックスって面倒なわりにできることが中途半端じゃない? これ便利なもんなの? 使いどころを考えるのも難しそうなんだが。
あとさ、制約のせいで面倒になるなら、いっそ使わないほうがいいんじゃない? プログラム側でバリデーションしたほうがいいんでない? どうせ制約では簡単なことしかできないのだから。外せないのはPK
くらいか?
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
- SQLite 3.29.0
- MeCab 0.996ユーザ辞書
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux
前回まで
- SQLite3学習 俯瞰まとめ
- SQLite3学習 環境構築まとめ
- SQLite3学習 インタフェースまとめ(C言語、CLI、対話モード、Tcl...)
- SQLite3学習 ドットコマンドまとめ
- SQLite3学習 JSON拡張まとめ
- SQLite3学習 FTSまとめ(ICU, MeCab)
- SQLite3学習 再帰クエリ(WITH RECURSIVE)
- SQLite3学習 R-Treeモジュール
- SQLite3学習 Geopoly(2次元ベクタ画像の生成)
- SQLite3学習 拡張関数(generate_series)
- SQLite3学習 拡張ライブラリ数学関数(extension-functions.c)
- SQLite3学習 謎と名前
- SQL構文 alter(rename)
- SQL構文 alter(add column)概要
- SQL構文 alter(add column)制約
- SQL構文 alter(add column)sqlite_master変更しても反映されない