こんにちは、かつコーチです。
ファイルベースの仕組みを理解したところで、今回はSQLiteを使う上で必ずつまずくデータ型の話をします。
MySQLやPostgreSQLに慣れていると、SQLiteのデータ型の扱いには驚くはずです。
「なぜINTEGER型のカラムに文字列を入れてもエラーにならないの?」という疑問を解消していきましょう。
SQLiteのデータ型とは?
わずか5種類しかないストレージクラス
SQLiteでは、値が実際にどう保存されるかを表すストレージクラスが5種類しかありません。
| ストレージクラス | 内容 |
|---|---|
| NULL | 値が存在しないことを表す |
| INTEGER | 符号付き整数 |
| REAL | 浮動小数点数 |
| TEXT | 文字列(UTF-8/UTF-16) |
| BLOB | バイナリデータ(入力された値そのまま) |
MySQLのVARCHAR(255)やDATETIMEのような、細かく分かれたデータ型は存在しません。
これはSQLiteが動的型付けを採用しているためで、カラムではなく「値そのもの」がどの型かを保持する仕組みになっています。
型を宣言してもチェックされるわけではない
SQLiteでは、CREATE TABLEで好きな型名を書くことができますが、それは強制力を持ちません。
CREATE TABLE members (
id INTEGER PRIMARY KEY,
age INTEGER,
memo TEXT
);
-- age(INTEGER型のはず)に文字列を入れてもエラーにならない
INSERT INTO members (id, age, memo) VALUES (1, '二十歳', 'テスト');
MySQLであれば型不一致でエラーになる、あるいは自動変換されて警告が出るようなケースでも、SQLiteはそのまま受け入れてしまいます。
この挙動を知らずに設計すると、意図しないデータが混入する原因になります。
型アフィニティという独自の仕組み
カラムに「型の推奨傾向」を持たせる仕組み
とはいえSQLiteにも、型に近い挙動をさせる仕組みがあります。
それが型アフィニティ(column affinity)です。
型アフィニティとは、カラムに宣言した型名から「このカラムはできればこの型で保存してほしい」という推奨傾向を決める仕組みです。
SQLiteは、カラムの宣言型に含まれる文字列を見て、5種類のアフィニティのどれかに自動的に分類します。
| 宣言型に含まれる文字列 | 割り当てられるアフィニティ | 例 |
|---|---|---|
| “INT” | INTEGER | INTEGER, BIGINT, INT |
| “CHAR”, “CLOB”, “TEXT” | TEXT | VARCHAR(255), TEXT |
| “BLOB”、または型未指定 | BLOB | BLOB |
| “REAL”, “FLOA”, “DOUB” | REAL | REAL, FLOAT, DOUBLE |
| 上記以外 | NUMERIC | DECIMAL(10,2), BOOLEAN, DATE |
つまりVARCHAR(255)と書いても、SQLiteの内部的には単に「TEXTアフィニティ」として扱われるだけで、桁数の255は無視されます。
アフィニティによる自動変換の挙動
型アフィニティが設定されたカラムに値を挿入すると、SQLiteはその値をアフィニティに合わせて変換を試みます。
CREATE TABLE products (
id INTEGER PRIMARY KEY,
price NUMERIC -- NUMERICアフィニティ
);
INSERT INTO products (id, price) VALUES (1, '1000'); -- 文字列で挿入
SELECT typeof(price) FROM products WHERE id = 1;
-- => integer ('1000'が数値に変換されて格納される)
一方、数値に変換できない文字列を入れた場合は、変換されずTEXTのまま保存されます。
INSERT INTO products (id, price) VALUES (2, '千円');
SELECT typeof(price) FROM products WHERE id = 2;
-- => text (変換できないのでそのまま)
このように「できる範囲で変換を試み、無理なら元の型のまま保存する」というのが、型アフィニティの基本挙動です。
よくあるつまずきポイント:日付・真偽値の扱い
DATE型が無いことに戸惑う
❌ Before:DATE型があると思い込んで実装する
CREATE TABLE events (
id INTEGER PRIMARY KEY,
event_date DATE
);
INSERT INTO events (id, event_date) VALUES (1, '2026-09-02');
一見動きますが、event_dateはNUMERICアフィニティになり、文字列'2026-09-02'は数値に変換できないためTEXTとしてそのまま保存されます。
私が初めてSQLiteでアプリを作ったとき、日付の比較や範囲検索で結果がおかしいと感じ、原因を調べたところ「型は文字列として保存されているだけで、日付として解釈されていない」ことに気づきました。
✅ After:ISO 8601形式の文字列として統一し、日付関数を使う
-- YYYY-MM-DD形式(ISO 8601)で統一すれば文字列比較がそのまま日付比較になる
SELECT * FROM events
WHERE event_date BETWEEN '2026-09-01' AND '2026-09-30';
-- SQLiteの日付関数でも扱える
SELECT date(event_date, '+7 days') FROM events WHERE id = 1;
SQLiteに専用のDATE型はありませんが、YYYY-MM-DD形式の文字列で統一しておけば、文字列としての大小比較がそのまま日付の前後比較として機能します。
これはSQLiteの公式ドキュメントでも推奨されている運用方法です。
真偽値(BOOLEAN)は実質INTEGER
SQLiteにBOOLEAN型はなく、TRUE/FALSEは内部的に整数の1/0として保存されます。
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
is_done BOOLEAN
);
INSERT INTO tasks (id, is_done) VALUES (1, TRUE);
SELECT typeof(is_done) FROM tasks WHERE id = 1;
-- => integer
アプリ側の言語(Pythonなど)でBOOLEAN型として扱っていても、DB上では整数であることを意識しておくと、他ツールでのデータ確認時に混乱しません。
応用・一歩先の使い方:STRICT テーブルで厳密な型チェック
SQLiteのバージョン3.37以降では、STRICTキーワードを付けることで、型アフィニティによる緩い変換ではなく、厳密な型チェックを行うテーブルを作成できます。
CREATE TABLE strict_products (
id INTEGER PRIMARY KEY,
price REAL
) STRICT;
-- STRICTテーブルでは型に合わない値の挿入はエラーになる
INSERT INTO strict_products (id, price) VALUES (1, 'not a number');
-- => Runtime error: stepping, cannot store TEXT value in REAL column
「動的型付けの緩さが不安」という中〜上級者には、このSTRICTテーブルを積極的に使うことをおすすめします。
ただし対応バージョンに依存するため、利用しているSQLiteのバージョンを事前に確認しておきましょう。
まとめ
この記事のポイント
- SQLiteのストレージクラスはNULL・INTEGER・REAL・TEXT・BLOBの5種類のみ
- 宣言した型は強制力を持たず、実際の型変換は「型アフィニティ」というルールで決まる
- DATE型・BOOLEAN型は存在せず、TEXTやINTEGERで代用する運用が一般的
- 厳密な型チェックが必要なら
STRICTテーブルを使う
次に読むべき記事
データ型の独自ルールが分かったところで、次は複数の処理を安全に実行するためのトランザクションと同時アクセスの注意点を見ていきましょう。
→ 次の記事:トランザクションと同時アクセスで気をつけたいこと
タグ: SQLite, 中級者向け, データ型