ビューは「名前を付けた SELECT 文」
SQLite のビュー(view)とは、名前を付けて保存した SELECT 文のことです。一度作ってしまえば、まるでテーブルのように SELECT できますが、データそのものは保存されません。ビューを参照するたびに、SQLite は裏側のクエリを毎回実行し直します。
paid_orders は見た目も動きもテーブルそのものです。カラムもあるし、SELECT もできるし、JOIN だってできます。ただし内部的には、クエリを実行するたびに元の WHERE status = 'paid' フィルタへ展開されます。
ビューのイメージはこれだけ覚えればOKです。ビューはクエリにつけた別名、それだけのこと。
sqlite ビューが便利な理由
一番のメリットは「名前をつけられる」という点です。ややこしいクエリに短くて分かりやすい名前を与えられるので、呼び出す側のコードがすっきり読みやすくなります。
ビューを使わない場合、呼び出し側が毎回自分で GROUP BY を書くことになります。誰か一人でもフィルタを書き間違えれば、それで終わりです。ビューにしておけば集計ロジックは一箇所に定義されるので、呼び出す側は customer_totals を参照して、必要に応じて追加のフィルタを重ねるだけで済みます。
ビューは権限の境界線としても役立ちます。たとえば password_hash カラムをクエリで露出させたくないなら、そのカラム 以外 をすべて SELECT するビューを作って、アプリ側のコードはそのビュー経由でアクセスするようにします。
CREATE VIEW の構文
完全な書き方は次のとおりです。
CREATE [TEMPORARY] VIEW [IF NOT EXISTS] view_name [(column_aliases)] AS
SELECT ...;
いくつか押さえておきたいポイントがあります。
IF NOT EXISTSを付けると、すでに同名のビューがある場合はエラーを出さずに作成をスキップします。TEMPORARY(またはTEMP)を使うと、接続が閉じたタイミングで消える一時ビューを作れます。- カッコでカラムのエイリアスを指定すれば、元の
SELECTをいじらずにビュー側のカラム名だけを変更できます。
このビューを使えば、元テーブルのカラム名を変えなくても、item や dollars といったわかりやすい名前でアクセスできます。
sqlite ビューの置き換えと削除
SQLite には CREATE OR REPLACE VIEW や ALTER VIEW は用意されていません。ビューの定義を変更したいときは、いったん削除してから作り直す必要があります。
DROP VIEW IF EXISTS active_orders; が安全な書き方です。ビューが存在しなくてもエラーになりません。ビューを削除しても元になったテーブルには一切影響しません。あくまで保存されていたクエリを消すだけです。
SQLite の一時ビュー
TEMP VIEW は、現在のデータベース接続が生きている間だけ有効なビューです。接続を閉じると同時に消えます。定義をデータベースに残したくない、その場限りの分析作業にぴったりです。
一時ビューを使うと、スキーマに正式に登録しないままクエリ名を一時的に上書きできるので、試行錯誤の段階で重宝します。
SQLite のビューはデフォルトで読み取り専用
ここが一番ハマりやすいポイントです。ビューに対して直接 INSERT・UPDATE・DELETE を実行することはできません。
sqlite> INSERT INTO paid_orders (customer, amount) VALUES ('Eve', 50);
Runtime error: cannot modify paid_orders because it is a view
解決策は INSTEAD OF トリガーです。書き込み操作の代わりに発火するトリガーを定義し、その中で実テーブルに対する本来の操作へ変換してあげます。
ビュー自体はビューのままですが、これで書き込みにも逃げ道ができました。トリガーについては次のページでしっかり取り上げます。
マテリアライズドビューはない — 自分で作る
データベースによっては、ビューの結果をディスクにキャッシュしておいて好きなタイミングで更新できるものもありますが、SQLite にはその機能がありません。ビューを読むたびに、裏側のクエリが毎回実行されます。たいていのワークロードではこれで十分です。SQLite は高速ですし、クエリプランナーもよくできています。ただし、重い集計を何度も叩くようなケースでは、実テーブルを用意して自分で同期する形にするのが現実的です。
そうしたら、定期的にキャッシュを再構築するか、orders テーブルにトリガーを仕掛けて常に最新の状態を保つようにします。手間はかかりますが、SQLite ではこれしか方法がありません。
ビューの一覧を取得する
ビューのメタデータは、テーブルやインデックスと同じく sqlite_master に格納されています。
sql カラムには元の CREATE VIEW 文がそのまま入っているので、「このビュー何だっけ?」というときに便利です。CLI なら .schema view_name でもっと見やすく同じ内容を確認できます。
ビューを使うべき場面
sqlite ビューが活きるのは次のようなケースです。
- そこそこ複雑なクエリを 3 か所以上で使い回している場合。一度名前を付けてしまえば、コピペを繰り返す必要はありません。
- アプリの一部に対して、特定のカラムや行だけを切り出して見せたいとき。
- 集計結果が概念的にひとつのまとまりになっていて (
monthly_sales、active_usersなど)、呼び出し側からは「ひとつの名詞」として扱わせたいとき。
逆に、こんなときはビューにしない方が無難です。
- そのクエリを使うのが 1 か所だけのとき。素直にインラインで書きましょう。
- パフォーマンスが重要で、元のクエリが重い場合。読み取りのたびにそのコストを払うことになります。実テーブルにキャッシュする方が現実的です。
- ビューがビューを参照し、さらにそれが別のビューを参照している…という構造になっているとき。SQLite 自体はネストを問題なく処理しますが、3 段、4 段と重なると、デバッグ時に実際の SQL を追うのがつらくなります。
次はトリガー
ビューとトリガーはセットで語られることが多いトピックです。ビューを書き込み可能にする INSTEAD OF パターンは、トリガーが存在する大きな理由のひとつ。もちろんトリガー単体でも、監査ログ、カスケード更新、不変条件の維持などに役立ちます。次のページではそのあたりを見ていきます。
よくある質問
SQLiteのビュー(VIEW)とは?
ビューは、保存しておいた SELECT 文をテーブルのように扱える仕組みです。データ自体は持たず、参照するたびに裏側のクエリが毎回実行されます。複雑なクエリに名前を付けて使い回したり、呼び出し側に見せたくないカラムを隠したいときに便利です。
ビュー経由で INSERT や UPDATE はできる?
そのままではできません。SQLiteのビューは読み取り専用なので、INSERT・UPDATE・DELETE を直接実行するとエラーになります。書き込みたい場合は INSTEAD OF トリガーを定義し、ビューへの書き込みを実テーブルへの操作に変換してあげる必要があります。
SQLiteはマテリアライズドビューに対応している?
対応していません。SQLiteにあるのは通常の(仮想的な)ビューだけで、参照のたびにクエリが走ります。結果をキャッシュしたい場合は、普通のテーブルを作って自分でリフレッシュするか、トリガーで元テーブルと同期させる方法が現実的です。
データベース内のビュー一覧を取得するには?
sqlite_master を参照すればOKです。SELECT name FROM sqlite_master WHERE type = 'view'; で一覧が取れます。CLIなら .schema で CREATE VIEW 文が確認でき、.tables でテーブルと一緒にビューも表示されます。