
2026/08/25 8:00
ドキュメントデータベースとしての SQLite(2020)
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
2020 年 1 月 22 日(バージョン 3.31.0)にリリースされた SQLite は、「生成列」を導入し、生 JSON の挿入と索引フィールドの自動抽出を可能にすることで、効果的にドキュメントストアとして機能するようになります。ユーザーは
GENERATED ALWAYS AS 構文を用いて生成列を定義でき(例:JSON 文字列から $.d を抽出)、読み取り時のリアルタイム計算用の VIRTUAL 列と、キャッシュされた値用の STORED 列のどちらかを選択できます。STORED 列はテーブル作成後の追加はできませんが、既存のテーブルに対しては ALTER TABLE ... ADD COLUMN を用いて新しい仮想生成列とその対応する索引を追加することが可能です。このアーキテクチャは、生ペイロード(例:webhook から)を最初に取り込み、使用パターンに応じて後に構造化されたフィールドを抽出するという柔軟なワークフローをサポートします。特定の JSON キーが存在することを確保するため、生成列に NOT NULL などの制約を適用できます。不整合のある JSON を挿入すると即座にエラーが発生し(「Error: malformed JSON」または「Error: NOT NULL constraint failed」)、複雑なアプリケーションコードなしでデータ整合性を確保します。開発者は CREATE INDEX や EXPLAIN QUERY PLAN のような標準 SQL コマンドを使用して、生成列上の索引がクエリの速度を著しく向上させることを確認でき、単なる保存と強力なドキュメントデータベース機能とのギャップを橋渡しできます。最近のバージョン(例:3.31.1)は macOS で Homebrew を通じて利用可能で、古いシステムでは nixpkgs-unstable のような不安定なソースが必要となる場合があります。これらの例は 2020 年 6 月 17 日の「in code」シリーズで発表されました。本文
SQLite の JSON データに対する生成されたカラム機能を活用する方法
SQLite は長年 JSON サポートを持っていましたが、**バージョン 3.31.0(2020 年 1 月)**で「生成されたカラム(Generated Columns)」機能が追加されました。これにより、軽量な埋め込みデータベースとしても文書データベースのように扱うことが可能になります。
基本動作:JSON からカラムを抽出してインデックス化
JSON データをそのまま INSERT した後、特定のフィールドを抽出して VIRTUAL カラムとして定義し、その値で検索できるようにします。
準備と基本的なテーブル作成:
$ sqlite3 SQLite version 3.31.1 2020-01-27 19:55:54 Connected to a transient in-memory database. sqlite> CREATE TABLE t ( body TEXT, d INT GENERATED ALWAYS AS (json_extract(body, '$.d')) VIRTUAL ); sqlite> INSERT INTO t VALUES(json('{"d":"42"}')); sqlite> SELECT * FROM t WHERE d = 42;
出力:
{"d":"42"}|42
カラムは、INSERT 時に提供された JSON から自動的に抽出されます。d- 重要: 表構造を変更せずに、動的なインデックス化が可能になります。
バージョンと環境に関する注意点
この機能には十分に新しいバージョンの SQLiteが必要です。
- macOS の Homebrew で対応されている場合があります。
- それ以外の場合は、
などの不安定版ソースを利用する必要があるかもしれません。nixpkgs-unstable
データの整合性を強制する方法
通常、JSON は
json() 関数で最小化(ミニファイ)して検証するのが推奨されますが、SQLite には「純粋な JSON タイプ」がありません。そのため、何でも許容してしまう欠点があります。
GENERATED ALWAYS AS (json_extract(...)) を使うと、不正な JSON を挿入しようとするとエラーが発生します。
1. 非正規な JSON の検出:
sqlite> INSERT INTO t VALUES(json('{"d": "malformed"}')); -- エラー: malformed JSON
2. NOT NULL 制約の追加:
特定のキーが存在しない場合や空文字列を挿入すると、抽出された値が NULL となり
NOT NULL 制約に違反します。
sqlite> CREATE TABLE x ( body TEXT, id TEXT GENERATED ALWAYS AS (json_extract(body, '$.id')) VIRTUAL NOT NULL ); sqlite> INSERT INTO x VALUES(''); -- エラー: malformed JSON sqlite> INSERT INTO x VALUES('{}'); -- エラー: NOT NULL constraint failed: x.id
ストアード(キャッシュ)カラムとの違い
生成されたカラムには
VIRTUAL と STORED の 2 つのタイプがあります。
- VIRTUAL: データを計算して格納せず、必要時だけ評価します(上記例で使用)。
- STORED: 値を物理的にキャッシュできますが、
で追加できないという大きな制限があります。ALTER TABLE
無論、
VIRTUAL カラムでもインデックスは追加可能です。
インデックスの作成と管理
仮想カラムであっても、標準的な SQL でインデックスを作成・管理できます。
単一のカラムにインデックスを追加:
CREATE INDEX xid ON x(id); -- 動作確認 EXPLAIN QUERY PLAN SELECT * FROM x WHERE id='foo'; -- 出力: --SEARCH TABLE x USING INDEX xid (id=?)
ALTER TABLE
と組み合わせる拡張ワークフロー
ALTER TABLEALTER TABLE を使うことで、テーブルに新たなカラムとインデックスを追加しながら拡張できます。最初はシンプルな JSON コラムだけから始め、必要なフィールドが見つかった段階でインデックス化します。
ステップバイステップでの拡張例:
-- 1. テキスト列のカラム追加とインデックス化 ALTER TABLE x ADD COLUMN text TEXT GENERATED ALWAYS AS (json_extract(body, '$.text')) VIRTUAL; -- 2. データの挿入 INSERT INTO x VALUES(json('{"id":43, "text":"test"}')); -- 3. 新しいカラムへのインデックス追加 CREATE INDEX xtext ON x(text);
このアプローチの利点
- 段階的な進化: シンプルな JSON テーブルから始め、需要に合わせてカラムとインデックスを追加できます。
- Webhook 処理への適合: 受信データをそのまま保存し、後で必要なフィールドだけを抽出・利用するワークフローが容易です。
- 軽量さ: Elastic や PostgreSQL と比べて、埋め込みデータベースとしての負荷が少ないメリットがあります。
楽しい時間を過ごしてください。