VIEW(ビュー)とは、SELECT文に名前をつけて保存した仮想テーブルです。実データを持たず、参照するたびに定義のSELECTが内部で実行されます。使いどころは主に3つあります。複雑なJOINを一行で呼び出す再利用、特定カラムだけ公開するセキュリティ制御、そしてビジネスロジックの一か所への集約です。一方で更新できるビューとできないビューがあり、アルゴリズムの違いが性能に直結するケースもあります。この記事では基本構文から更新可能ビューの条件、落とし穴まで実例を交えて整理します。
このシナリオで考える
以下の注文テーブルを用意します。statusにはpending(処理待ち)・shipped(発送済み)・cancelled(キャンセル)の3値があります。以降のVIEW例はすべてこのテーブルをベースに動かします。
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status ENUM('pending','shipped','cancelled') NOT NULL DEFAULT 'pending',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO orders (customer_id, amount, status, created_at) VALUES
(1, 5800.00, 'shipped', '2026-06-01 10:00:00'),
(2, 12000.00, 'pending', '2026-06-02 09:15:00'),
(1, 3200.00, 'cancelled', '2026-06-02 11:30:00'),
(3, 7500.00, 'shipped', '2026-06-03 14:30:00'),
(2, 9900.00, 'pending', '2026-06-04 08:00:00');
CREATE VIEWの基本構文と操作方法
ビューを作成するにはCREATE VIEW ビュー名 AS SELECT文と書きます。たとえばshipped(発送済み)の注文だけを返すビューは次のように定義できます。
CREATE VIEW shipped_orders AS
SELECT id, customer_id, amount, created_at
FROM orders
WHERE status = 'shipped';
作成後は通常のテーブルと同じようにSELECTできます。
SELECT * FROM shipped_orders;
+----+-------------+---------+---------------------+
| id | customer_id | amount | created_at |
+----+-------------+---------+---------------------+
| 1 | 1 | 5800.00 | 2026-06-01 10:00:00 |
| 4 | 3 | 7500.00 | 2026-06-03 14:30:00 |
+----+-------------+---------+---------------------+
MySQLのデフォルトはMERGEアルゴリズムで、ビューの定義を呼び出し元クエリに展開してから実行します。「shipped_ordersをSELECTした」は内部では「ordersテーブルに対してWHERE status = 'shipped'でSELECTした」と同じように処理されます。オーバーヘッドがほとんどなく、ベーステーブルのインデックスもそのまま効きます。
主なビュー操作コマンドをまとめます。
| 操作 | コマンド |
|---|---|
| 作成・置換 | CREATE OR REPLACE VIEW v AS SELECT ...; |
| 定義変更 | ALTER VIEW v AS SELECT ...; |
| 削除 | DROP VIEW IF EXISTS v; |
| 定義確認 | SHOW CREATE VIEW v; |
| ビュー一覧 | SHOW FULL TABLES WHERE Table_type = 'VIEW'; |
既存ビューを更新したいときはCREATE OR REPLACE VIEWを使うと、DROPとCREATEを一命令で置き換えられます。依存するオブジェクトがある場合も安全に定義を差し替えられます。
JOINを含むビューの実用例
VIEWが最も力を発揮するのは、複数テーブルを結合する複雑なSELECTに名前をつけて隠すときです。顧客テーブルと注文テーブルを結合した例を見てみましょう。
CREATE TABLE customers (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL
);
INSERT INTO customers (id, name) VALUES
(1, '山田 太郎'), (2, '鈴木 花子'), (3, '田中 一郎');
-- JOIN をカプセル化したビュー
CREATE VIEW order_details AS
SELECT o.id AS order_id,
c.name AS customer_name,
o.amount,
o.status,
o.created_at
FROM orders o
JOIN customers c ON c.id = o.customer_id;
このビューを使えば、毎回JOINを書かずに済みます。
-- 発送済みの注文一覧を顧客名つきで取得
SELECT * FROM order_details
WHERE status = 'shipped'
ORDER BY created_at;
+----------+---------------+---------+---------+---------------------+
| order_id | customer_name | amount | status | created_at |
+----------+---------------+---------+---------+---------------------+
| 1 | 山田 太郎 | 5800.00 | shipped | 2026-06-01 10:00:00 |
| 4 | 田中 一郎 | 7500.00 | shipped | 2026-06-03 14:30:00 |
+----------+---------------+---------+---------+---------------------+
JOINビューもMERGEアルゴリズムで処理されるため、ベーステーブルのインデックスが有効です。アプリ側からはorder_detailsを普通のテーブルのように扱えます。テーブル構造が変わったときの修正箇所をビュー定義の一か所に集約できるため、保守性も上がります。
更新可能ビューと更新不可ビューの違い
VIEWに対してUPDATE・INSERT・DELETEを実行できる場合があります。これを「更新可能ビュー(Updatable View)」と呼びます。更新可能になるのは、以下の条件をすべて満たすときです。
- 集約関数(SUM・COUNT・MAX・MIN・AVG)を含まない
- GROUP BY・HAVINGを含まない
- DISTINCTを含まない
- UNION・UNION ALLを含まない
- FROM句にサブクエリを含まない
- 更新するカラムが1つのベーステーブルに直接対応している
先ほどのshipped_ordersはこれらをすべて満たすため、次のUPDATEが動作します。実際にはordersテーブルの該当レコードが直接更新されます。
-- orders テーブルの id=1 が直接更新される
UPDATE shipped_orders SET amount = 6000.00 WHERE id = 1;
WITH CHECK OPTIONで整合性を守る
WITH CHECK OPTIONを付けると、ビューのWHERE条件を外れる変更を拒否します。誤ってビューの定義外のデータを更新・挿入してしまうのを防ぐ安全策です。
CREATE OR REPLACE VIEW active_orders AS
SELECT id, customer_id, amount, status
FROM orders
WHERE status != 'cancelled'
WITH CHECK OPTION;
-- NG: status を cancelled にしようとするとエラー
-- ERROR 1369 (HY000): CHECK OPTION failed 'mydb.active_orders'
UPDATE active_orders SET status = 'cancelled' WHERE id = 1;
-- OK: 条件を満たす変更はそのまま通る
UPDATE active_orders SET amount = 6500.00 WHERE id = 1;
更新不可ビューになる例
GROUP BYや集約関数を含むビューは更新不可です。実行するとエラーが返ります。
CREATE VIEW customer_totals AS
SELECT customer_id,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id;
-- Error: The target table customer_totals of the UPDATE is not updatable
UPDATE customer_totals SET total_amount = 0 WHERE customer_id = 1;
セキュリティとアクセス制御への活用
VIEWの実用的な使いどころの一つが、テーブルへの直接アクセスを隠しながら必要なデータだけを公開するアクセス制御です。たとえば金額(amount)を担当者に見せたくない場合、次のように対応できます。
-- 金額カラムを除いた公開用ビュー
CREATE VIEW orders_public AS
SELECT id, customer_id, status, created_at
FROM orders;
-- 担当者ロールにはビューの SELECT だけ許可
GRANT SELECT ON mydb.orders_public TO 'operator'@'%';
-- orders テーブルへの直接アクセスは与えない
SQL SECURITY句でビューの実行権限のコンテキストも制御できます。
| 設定 | 実行権限の基準 | 使いどころ |
|---|---|---|
| SQL SECURITY DEFINER(デフォルト) | ビューを作成したユーザー | 呼び出し元がテーブル権限を持たなくてもアクセスさせたい場合 |
| SQL SECURITY INVOKER | ビューを呼び出したユーザー | 呼び出し元の権限をそのまま反映させたい場合 |
DEFINERを使うと、管理者が作成したビューを介して一般ユーザーが安全にデータを参照できます。テーブルそのものへの権限を与えずに済むため、最小権限の原則に沿った設計がしやすくなります。
よくある落とし穴と注意点
TEMPTABLEアルゴリズムは性能を落とす
MySQLのビュー処理にはMERGEとTEMPTABLEの2アルゴリズムがあります。MERGEはビュー定義を呼び出し元クエリに展開するため、ベーステーブルのインデックスをそのまま利用できます。一方、TEMPTABLEは一時テーブルを経由するため、インデックスが効かず大量データでは遅くなります。
GROUP BY・DISTINCT・UNION・集約関数を含むビューは自動的にTEMPTABLEになります。EXPLAINでselect_typeがDERIVEDと表示されたらTEMPTABLE相当の処理が走っていると考えてください。
-- TEMPTABLE になる例(GROUP BY あり)
EXPLAIN SELECT * FROM customer_totals WHERE customer_id = 1;
-- select_type: DERIVED、type: ALL と表示されることが多い
集計ビューを高頻度で呼ぶ場合は、都度CTEやサブクエリで書き直す、あるいは集計結果をサマリーテーブルに持つ設計を検討しましょう。
ベーステーブルの変更でビューが無効化される
ビューが参照するテーブルのカラムを削除・リネームしたり、テーブルそのものをDROPしたりすると、ビューが壊れます。エラーは参照時まで表面化しないため、変更後に気づかないまま時間が経つことがあります。定期的にCHECK TABLE shipped_orders;を実行してビューの状態を確認すると安心です。
ビューのネストは最小限に留める
ビューの中でさらにビューを参照する「多段ネスト」は可能ですが、依存関係が増えるほど保守が難しくなります。複数のビューが絡み合う場合はCTEや直接のJOINで書き直す方が追跡しやすくなります。
まとめ
VIEWは複雑なSELECTをカプセル化して再利用性・セキュリティ・保守性を高める仕組みです。集約やDISTINCTのない単純な構造なら更新も可能で、WITH CHECK OPTIONで整合性を守れます。GROUP BYや集約関数を含むとTEMPTABLEアルゴリズムとなり性能への影響に注意が必要です。ベーステーブルの変更がビューを壊す点も頭に入れておくと、安心して活用できます。
参考リンク
アイキャッチ画像: Photo by Jantine Doornbos on Unsplash
