3.

SQL ビュー (View) 完全ガイド (Materialized/Updatable)

編集
この記事の要点
  • ビュー (View)仮想テーブル。実体を持たず SELECT 文の別名
  • 基本構文: CREATE VIEW v AS SELECT ...、置換は CREATE OR REPLACE VIEW
  • Updatable View: 単一テーブルベースで条件を満たせば INSERT/UPDATE/DELETE 可
  • Materialized View: クエリ結果を実体化(キャッシュ)して高速化、Oracle/PG が対応
  • 用途: 権限管理 / 複雑クエリの抽象化 / レポート定義の再利用

ビューとは何か

ビューは名前付きの SELECT 文です。実テーブルではないので、データの実体はビューには存在しません。アクセスのたびに元の SELECT が実行され、その結果が返されます。

-- 基本のビュー定義
CREATE VIEW active_users AS
SELECT id, name, email
FROM users
WHERE status = 'active' AND deleted_at IS NULL;

-- 使い方は普通のテーブルと同じ
SELECT * FROM active_users WHERE email LIKE '%@example.com';

-- 結合や集約を含むビュー
CREATE VIEW user_order_summary AS
SELECT
    u.id AS user_id,
    u.name,
    COUNT(o.id) AS order_count,
    COALESCE(SUM(o.amount), 0) AS total_amount,
    MAX(o.created_at) AS last_order_at
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;

SELECT * FROM user_order_summary WHERE order_count >= 10;

主なメリット

メリット具体例
複雑クエリの抽象化10 個の JOIN を 1 つの SELECT に隠す
権限の粒度管理users テーブル全体は見せず、特定列のみのビューに GRANT
後方互換性テーブル分割後も旧アプリ向けに統合ビューを残す
レポート定義の再利用BI ツールがアクセスするビューを統一
論理的なデータモデル物理スキーマと業務ビューを分離

ビューの作成・変更・削除

-- 作成
CREATE VIEW v_name AS SELECT ... ;

-- 既存があれば置換(推奨)
CREATE OR REPLACE VIEW v_name AS SELECT ... ;

-- 削除
DROP VIEW v_name;

-- 依存ビューも巻き込んで削除
DROP VIEW v_name CASCADE;       -- PostgreSQL / Oracle

-- ビュー一覧(PostgreSQL)
SELECT viewname FROM pg_views WHERE schemaname = 'public';

-- ビュー一覧(MySQL)
SHOW FULL TABLES IN dbname WHERE Table_type = 'VIEW';

-- ビュー定義の確認
SELECT view_definition FROM information_schema.views WHERE table_name = 'v_name';
-- Oracle
SELECT text FROM user_views WHERE view_name = 'V_NAME';

Updatable View(更新可能ビュー)

ビューに対して INSERT / UPDATE / DELETE を実行すると、内部的に元のテーブルに反映されます。条件:

  • 単一テーブル由来であること(JOIN なし)
  • DISTINCT / GROUP BY / HAVING / 集約関数 / UNION なし
  • サブクエリで参照されるテーブルが同じでないこと(DB により差)
  • すべての非 NULL カラムがビューに含まれている(INSERT する場合)
-- Updatable View の例
CREATE VIEW active_users AS
SELECT id, name, email, status
FROM users
WHERE deleted_at IS NULL;

-- 更新できる
UPDATE active_users SET status = 'banned' WHERE id = 1;
-- → 元の users テーブルが更新される

-- WITH CHECK OPTION を付けると、ビュー条件を満たさない更新を拒否
CREATE OR REPLACE VIEW active_users AS
SELECT id, name, email, status, deleted_at
FROM users
WHERE deleted_at IS NULL
WITH CHECK OPTION;

UPDATE active_users SET deleted_at = NOW() WHERE id = 1;
-- → ERROR: CHECK OPTION failed (ビューから外れる更新を拒否)

INSTEAD OF トリガで複雑ビューも更新可能に

JOIN や集約を含むビューでも、INSTEAD OF トリガーを定義すれば更新可能になります(Oracle / SQL Server / PostgreSQL):

-- PostgreSQL の例
CREATE VIEW user_with_role AS
SELECT u.id, u.name, r.name AS role_name
FROM users u JOIN roles r ON u.role_id = r.id;

CREATE OR REPLACE FUNCTION trg_user_with_role_update()
RETURNS TRIGGER AS $$
BEGIN
  UPDATE users
  SET role_id = (SELECT id FROM roles WHERE name = NEW.role_name),
      name = NEW.name
  WHERE id = NEW.id;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER user_with_role_update_trg
INSTEAD OF UPDATE ON user_with_role
FOR EACH ROW EXECUTE FUNCTION trg_user_with_role_update();

Materialized View(実体化ビュー)

通常のビューは毎回 SELECT を実行しますが、Materialized View は結果をテーブルとして保存してアクセスを高速化します。集計レポート等の重いクエリに有効。

DB対応
OracleCREATE MATERIALIZED VIEW
PostgreSQLCREATE MATERIALIZED VIEW (9.3+)
SQL Server△ Indexed View で代用
MySQL× 標準では非対応 (テーブル + cron で代替)
-- PostgreSQL
CREATE MATERIALIZED VIEW user_sales_summary AS
SELECT
    u.id, u.name,
    COUNT(o.id) AS order_cnt,
    SUM(o.amount) AS total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;

-- 通常テーブルのようにアクセスできる
SELECT * FROM user_sales_summary WHERE total > 100000;

-- インデックスも貼れる
CREATE INDEX idx_uss_total ON user_sales_summary (total);

-- リフレッシュ(結果を再計算)
REFRESH MATERIALIZED VIEW user_sales_summary;

-- 同時アクセスを許可しながらリフレッシュ
REFRESH MATERIALIZED VIEW CONCURRENTLY user_sales_summary;
-- ★ CONCURRENTLY は UNIQUE INDEX が必須

-- Oracle: 高速リフレッシュ
CREATE MATERIALIZED VIEW LOG ON orders WITH PRIMARY KEY, ROWID INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW user_sales_summary
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS SELECT ... ;

Inline View(FROM 句のサブクエリ)との比較

-- Inline View: その場限り
SELECT *
FROM (
    SELECT department, AVG(salary) AS avg_sal
    FROM employees
    GROUP BY department
) t
WHERE t.avg_sal > 500000;

-- Named View: 再利用可能
CREATE VIEW dept_avg AS
SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department;

SELECT * FROM dept_avg WHERE avg_sal > 500000;
SELECT * FROM dept_avg WHERE department = 'sales';
項目Inline ViewNamed ViewMaterialized View
定義箇所クエリ内スキーマに永続スキーマに永続
実体化なしなしあり
再利用×
性能都度実行都度実行高速(古い可能性)
容量00

権限管理での活用

-- 機密列を隠したビュー
CREATE VIEW user_public AS
SELECT id, name, created_at
FROM users;
-- email / password_hash / ssn は含めない

-- ロールに権限付与
GRANT SELECT ON user_public TO reporter_role;
REVOKE SELECT ON users FROM reporter_role;
-- → reporter_role は users 全体は見えず、user_public 経由のみ

-- 行レベルフィルタ
CREATE VIEW own_orders AS
SELECT * FROM orders WHERE user_id = current_user_id();
-- → 自分の注文だけ見えるビュー

性能の落とし穴

  • ビューにビューを重ねるとクエリプランが肥大化し最適化が困難になる
  • SELECT * を使ったビューは元テーブルへの列追加で意図せず展開される
  • Updatable View に複雑な WITH CHECK OPTION を付けると更新性能が落ちる
  • Materialized View はリフレッシュコストが高い → 夜間バッチで実行が定番
  • Oracle の REFRESH ON COMMIT更新トランザクションを遅らせるので注意

FAQ

Q: ビューはテーブルより遅い?
A: 通常ビューは元クエリと同等の性能。オプティマイザはビュー展開して最適化します。Materialized View はテーブルより速いことが多いです。

Q: ビューにインデックスを貼れる?
A: 通常ビューには不可(元テーブルのインデックスを使う)。Materialized View には可能。SQL Server の Indexed View も類似機能。

Q: ビュー越しに DML を発行できる条件は?
A: 単一テーブルベース、集約・GROUP BY・DISTINCT・UNION なし。条件を満たさない場合は INSTEAD OF トリガで実装。

Q: CREATE VIEW でテーブル定義変更時の影響は?
A: Oracle: 依存ビューが INVALID に → 自動再コンパイル。MySQL: SELECT * のビューは列が固定化される。PostgreSQL: SELECT 文の列削除はビューが壊れる。

編集
Post Share
子ページ
  1. マテリアライズド・ビュー
同階層のページ
  1. 表(テーブル)
  2. 索引(インデックス)
  3. ビュー
  4. 制約
  5. 順序(シーケンス)
  6. シノニム
  7. トリガー
  8. パッケージ
  9. ストアド・ファンクション
  10. ストアド・プロシージャ
  11. ユーザー(USER)

最近更新/作成されたページ