SQLiteを使うなら「STRICT tables」一択!その理由を解説
Prefer strict tables in SQLite
Prefer strict tables in SQLite
SQLiteでテーブルを定義する際は、ぜひ「STRICT tables」モードを活用しましょう。通常のSQLiteは動的型付けが柔軟すぎて予期せぬバグを招きがちですが、STRICTモードを有効にすることで、厳格な型チェックが可能になります。これにより、データの整合性が格段に向上し、開発体験も快適になります。使い方は簡単で、CREATE TABLE文の最後に「STRICT」と記述するだけです。
てっきりこれがデフォルトだと思ってたよ。
STRICTがデフォルトになってほしいな。SQLiteの開発者には素晴らしいツールを作ってくれて感謝してるし、尊敬もしてるけど、そこだけは意見が合わないんだ。
動的なデータ型に合理性があるのはわかる(フラットなキーバリューストアみたいにね)。でも疑問なのは「何が一番直感的か」ってこと。INTEGER型なのに'hello world'をエラーなしで受け入れるのと、NONSTRICTやANY型を指定しない限りそんな値は挿入できない仕様と、どっちがしっくりくる?ほとんどのSQLiteユーザーに聞いたら、前者は予想外って答えるんじゃないかな。
Strictテーブルの欠点はDate型みたいな一部のデータ型が使えないことかな。でも本来はStrictがデフォルトであるべきだよね。複数のアプリでDBを共有してるなら、宣言したデータ型は信頼できるべきだし。あるアプリが数値カラムに文字列を突っ込んだら、他のアプリが全滅するわけだしね。一方で、SQLiteの主なユースケースは組み込みDBだから、アプリは1つだけでいい場合も多い。そのシナリオなら、DBを作り直してデータをコピーするより、スキーマを柔軟に変えられる今の仕様が利点になることもある。各カラムに何が入るか(混合データ型を含めて)をアプリ側のコードが把握できているなら尚更だね。
UUIDが数値として誤変換される現象に遭遇したことがあるよ(たしか08123…みたいに始まると8進数として解釈されちゃうやつ)。あれは混乱するし面倒だった。今は「strict」にしてテーブルを完全に作り直すことで解決したよ。
DBに関してはStrictな型を強く求める派だね。RPCも同じく。ただSQLiteはユースケースが少し特殊だから、もし使っていたら https://sqlite.org/flextypegood.html の主張も理解できるかもしれない。SQLite専用に作られていないランダムなスクリプトがそのまま動いてしまう、っていうのは他のDBMSではあまり考慮されない利点だし。
エンタープライズSQLの世界にいると、デフォルトで型チェックがないという理由だけでSQLiteを真面目に考えたことがなかったよ。(スマホのアプリメタデータでSQLiteが骨格になってると知ったときは愕然としたけどね)まあ、この記事を読んでネットワークの話を思い出したよ。最初は低遅延とシンプルさを求めてTCPじゃなくてUDPを選んだのに、結局アプリ側でリトライ処理とかTCPの信頼性機能を全部自前で実装する羽目になる、っていうあれと同じだね。
https://sqlite.org/flextypegood.html にこれがデフォルトにならない(そして恐らく今後もならない)理由が書いてあるよ。>「厳格な型強制はCustomer.creditScoreのようなINTEGERカラムへの文字列挿入を防げるが、もしミスが起きれば影響を受けた行を特定するのは非常に簡単だ」っていう理屈だけど、自分の経験とは一致しないな。特に破損した行を「修正」するのは簡単じゃないし、データが完全に失われることもあるし。>「簡単に検出できるエラーを抑圧し、検出しにくいエラーだけを通すことで、厳格な型強制は逆にバグの発見と修正を難しくする」という主張も、全く納得いかない。
CREATE TABLE ... STRICT WITHOUT ROWID が俺のデフォルト。これ以外にする理由がよくわからない。
>残念ながら、テーブルをALTERしてStrictにする方法はないみたい。非StrictなテーブルからStrictなテーブルへデータをコピーし直さないといけないはず。それを見て、自分のPythonライブラリ兼CLIツールのsqlite-utilsに機能を追加したよ。これでStrictテーブルへの変換が簡単になった。
uvx sqlite-utils transform data.db mytable --strict
Pythonならこんな感じ。
import sqlite_utils
db = sqlite_utils.Database("data.db")
db.table("mytable").transform(strict=True)
4.1のリリースノートはここ:https://sqlite-utils.datasette.io/en/stable/changelog.html#v4-1