この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:3
ページ更新者:guest
更新日時:2026-06-11 07:07:02

タイトル: ビュー
SEOタイトル: 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 文の列削除はビューが壊れる。