PostgreSQL: PostgreSQLのビューとマテリアライズドビュー

最終更新:2026-08-26

1. 学習目標


2. ストーリー

AliceはECプラットフォームのデータエンジニアです。運用チームは毎日8時に売上集計レポートを確認します。このレポートは5つのテーブルを結合し、300万件の注文レコードを集計するため、クエリに2時間かかります。

上司は「レポートを即座に返せないか?」と言いました。

Aliceはクエリをマテリアライズドビューとして定義し、毎日午前4時に自動更新するようにしました。事前計算されたレポートのクエリは2時間から3秒に短縮されました。ただし、マテリアライズドビューのデータは��アルタイムではありません。これが通常のビューとマテリアライズドビューのトレードオフです。


3. 概念: 通常のビュー

(1) CREATE VIEW構文

SQL
CREATE [OR REPLACE] VIEW view_name [(column_aliases)] AS
  SELECT ...;

ビューは保存されたクエリ定義であり、データは保存されません。ビューをクエリするたびに、PostgreSQLはビュー定義を展開(書き換え)して基になるクエリを実行します。

プロパティ 説明
保存内容 クエリテキストのみ
データ鮮度 リアルタイム、常に最新のベーステーブルデータを反映
パフォーマンス 基になるクエリを直接実行するのと同じ
使用容量 ほぼなし

▶ サンプル: 売上集計ビューの作成

SQL
CREATE VIEW v_daily_sales AS
SELECT
  order_date,
  region,
  COUNT(*)               AS order_count,
  SUM(amount)            AS total_revenue,
  AVG(amount)            AS avg_order_value
FROM orders
GROUP BY order_date, region;

Output:

TEXT 📖 参照専用
 count 
-------
     5
(1 row)

▶ サンプル: ビューのクエリ

SQL
SELECT * FROM v_daily_sales
WHERE order_date >= '2025-01-01'
ORDER BY total_revenue DESC;

Output:

TEXT 📖 参照専用
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

基になるSELECTを直接書くのと同等です。PostgreSQLが自動的に展開します。

(2) ALTER VIEWとDROP VIEW

操作 構文 説明
名前変更 ALTER VIEW v RENAME TO v_new ビュー名を変更
デフォルトカラム設定 ALTER VIEW v ALTER COLUMN c SET DEFAULT d カラムデフォルトを変更
所有者設定 ALTER VIEW v OWNER TO role 所有者を変更
削除 DROP VIEW [IF EXISTS] v [CASCADE] CASCADEで依存ビューも削除

▶ サンプル: ビューの変更と削除

SQL
ALTER VIEW v_daily_sales RENAME TO v_daily_summary;

DROP VIEW IF EXISTS v_daily_summary CASCADE;

Output:

TEXT 📖 参照専用
-- SQL statement executed successfully

(3) ビュー展開の仕組み

100%
flowchart LR
    A["SELECT * FROM v_daily_sales"] --> B["書き換え<br/>ビュー定義を展開"]
    B --> C["SELECT order_date, region, COUNT(*)...<br/>FROM orders<br/>GROUP BY ..."]
    C --> D[オプティマイザ]
    D --> E[実行]
    style B fill:#fff9c4
    style D fill:#c8e6c9

4. 概念: 更新可能ビュー

(1) どのビューが更新可��か

PostgreSQLでは、以下の条件をすべて満たす単純なビューが自動的に更新可能(INSERT / UPDATE / DELETEをサポート)になります。

条件 説明
FROMが単一のベーステーブルのみ JOINなし
GROUP BY / HAVINGなし 集計なし
DISTINCTなし 重複除去なし
ウィンドウ関数なし OVERなし
集合演算なし UNION / INTERSECT / EXCEPTなし
SELECTカラムがベーステーブルのカラム 式/計算カラムなし

▶ サンプル: 更新可能ビュー

SQL
CREATE VIEW v_active_users AS
SELECT user_id, name, email, status
FROM users
WHERE status = 'active';

Output:

TEXT 📖 参照専用
CREATE TABLE
SQL
UPDATE v_active_users SET name = 'Alice Wang' WHERE user_id = 1;

DELETE FROM v_active_users WHERE user_id = 99;

INSERT INTO v_active_users (user_id, name, email, status)
VALUES (101, 'Charlie', 'charlie@example.com', 'active');

(2) WITH CHECK OPTION

デフォルトでは、ビューを通じて更新/挿入された行はビューの範囲から「逃れる」ことができます(例: statusを'inactive'に変更すると、その行はビューに表示されなくなる)。WITH CHECK OPTIONはそのような逃避を禁止します。

オプション 動作
CHECK OPTIONなし 逃避許可。更新された行がビューに表示されなくなる可能性あり
WITH CHECK OPTION 逃避禁止。更新された行はビュー条件を満たす必要あり
WITH CASCADED CHECK OPTION 現在のビュー+依存ビューのチェック(再帰的)
WITH LOCAL CHECK OPTION 現在のビューの条件のみチェック

▶ サンプル: WITH CHECK OPTIONで逃避防止

SQL
CREATE VIEW v_active_users_strict AS
SELECT user_id, name, email, status
FROM users
WHERE status = 'active'
WITH CHECK OPTION;
SQL
UPDATE v_active_users_strict SET status = 'inactive' WHERE user_id = 1;
TEXT 📖 参照専用
ERROR: new row violates check option for view "v_active_users_strict"
DETAIL: Failing row contains (1, ..., inactive).

▶ サンプル: 更新可能ビューに挿入してからクエリ

SQL
INSERT INTO v_active_users (user_id, name, email, status)
VALUES (200, 'Bob', 'bob@example.com', 'active');

SELECT * FROM v_active_users WHERE user_id = 200;
TEXT 📖 参照専用
 user_id | name |       email        | status
---------+------+--------------------+--------
     200 | Bob  | bob@example.com    | active

5. 概念: マテリアライズドビュー

(1) MATERIALIZED VIEWの概要

マテリアライズドビューはクエリ結果を実際にディスクに保存します。クエリは事前計算されたデータを直接読み取り、基になるクエリを再実行しません。これはPostgreSQLの特長です。

プロパティ 通常のビュー マテリアライズドビュー
保存内容 クエリテキスト クエリテキスト+結果データ
データ鮮度 リアルタイム リフレッシュ時のみ更新
クエリパフォーマンス 基になるクエリと同じ 極めて高速(事前計算済みを読み取り)
使用容量 ほぼなし 結果セットと同じサイズ
更新可能性 単純なビューは可能 直接DML不可
リフレッシュ方法 不要 REFRESH MATERIALIZED VIEW

▶ サンプル: マテリアライズドビューの作成

SQL
CREATE MATERIALIZED VIEW mv_monthly_sales AS
SELECT
  DATE_TRUNC('month', order_date)::date AS month,
  region,
  COUNT(*)    AS order_count,
  SUM(amount) AS total_revenue,
  AVG(amount) AS avg_order_value
FROM orders
GROUP BY DATE_TRUNC('month', order_date), region
WITH DATA;

Output:

TEXT 📖 参照専用
 count 
-------
     5
(1 row)

▶ サンプル: マテリアライズドビューのクエリ

SQL
SELECT * FROM mv_monthly_sales
WHERE month >= '2025-01-01'
ORDER BY total_revenue DESC;

Output:

TEXT 📖 参照専用
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

ミリ秒単位で返ります。データが既に事前計算されて保存されているためです。

(2) REFRESH

構文 動作 ロック 速度
REFRESH MATERIALIZED VIEW mv 完全リフレッシュ、全データを置換 排他ロック取得、読み取りブ��ック 遅い
REFRESH MATERIALIZED VIEW CONCURRENTLY mv 増分リフレッシュ(一意インデックス必要) 読み取りブロックなし 高速

CONCURRENTLYはPostgreSQLの特長です。リフレッシュ中もビューがクエリ可能で、ビジネスがブロックされません。

▶ サンプル: 完全リフレッシュ

SQL
REFRESH MATERIALIZED VIEW mv_monthly_sales;

Output:

TEXT 📖 参照専用
-- SQL statement executed successfully

リフレッシュ中、このマテリアライズドビューへのすべてのSELECTがブロックされます。

▶ サンプル: CONCURRENTLY増分リフレッシュ

SQL
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_monthly_sales;

Output:

TEXT 📖 参照専用
-- SQL statement executed successfully

前提条件: マテリアライズドビューに少なくとも1つの一意インデックスが必要です。

SQL
CREATE UNIQUE INDEX idx_mv_monthly_sales_pk
  ON mv_monthly_sales (month, region);

REFRESH MATERIALIZED VIEW CONCURRENTLY mv_monthly_sales;

(3) WITH DATA と WITH NO DATA

オプション 動作
WITH DATA 作成時に即座にデータ投入(デフォルト)
WITH NO DATA 作成時は投入しない。最初のクエリ前にREFRESHが必要

▶ サンプル: 遅延投入

SQL
CREATE MATERIALIZED VIEW mv_expensive_report AS
SELECT ... FROM ... WITH NO DATA;

REFRESH MATERIALIZED VIEW mv_expensive_report;

Output:

TEXT 📖 参照専用
CREATE TABLE

▶ サンプル: 自動スケジュールリフレッシュ(pg_cron拡張機能)

SQL
SELECT cron.schedule(
  'refresh_monthly_sales',
  '0 4 * * *',
  $$REFRESH MATERIALIZED VIEW CONCURRENTLY mv_monthly_sales$$
);

Output:

TEXT 📖 参照専用
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

毎日午前4時に自動的にリフレッシュされます。


6. ビュー vs マテリアライズドビュー vs 一時テーブル

観点 通常のビュー マテリアライズドビュー 一時テーブル
クエリ定義を保存 はい はい いいえ
データを保存 いいえ はい はい
データ鮮度 リアルタイム 手動リフレッシュ 手動メンテナンス
クエリパフォーマンス 基になるクエリに依存 極めて高速 高速
クロスセッション永続性 はい はい いいえ(セッション終了で消滅)
インデックスサポート いいえ(ベーステーブルインデックス使用) はい はい
DMLサポート 単純なビューは更新可能 いいえ はい
典型的なシナリオ クエリの簡略化 レポート事前計算 一時的な中間結果
100%
classDiagram
    class View {
        +クエリテキストを保存
        +リアルタイムデータ
        +単純なビューはDML可能
        +WITH CHECK OPTION
    }
    class MaterializedView {
        +クエリテキスト+データを保存
        +REFRESHでリフレッシュ
        +CONCURRENTLY
        +インデックス可能
        +WITH DATA/NO DATA
    }
    class TempTable {
        +データのみ保存
        +セッション終了で消滅
        +完全にDML可能
        +インデックス可能
    }
    View <|-- MaterializedView : extends
    MaterializedView ..|> TempTable : 類似パフォーマンス

7. ビュー管理のベストプラクティス

(1) 命名規則

種別 推奨プレフィックス
通常のビュー v_ v_active_users
マテリアライズドビュー mv_ mv_monthly_sales
一時テーブル tmp_ tmp_import_data

(2) ビューの依存関係とセキュリティ

操作 リスク 解決策
ベーステーブル削除 ビューが無効になる DROP TABLE CASCADEで依存ビューを自動削除
ベーステーブルカラム変更 ビューがエラーになる可能性 CREATE OR REPLACE VIEWで定▶を更新
権限制御 ビューでカラム可視性を制限可能 GRANT SELECT ON view TO role

▶ サンプル: ビューによるカラムレベル権限

SQL
CREATE VIEW v_user_public AS
SELECT user_id, name
FROM users;

GRANT SELECT ON v_user_public TO reporter_role;

REVOKE SELECT ON users FROM reporter_role;

Output:

TEXT 📖 参照専用
CREATE TABLE

reporter_roleはuser_idとnameのみ表示可能で、emailなどの機密カラムは見られません。


8. 総合サンプル

Aliceのレポート最適化——マテリアライズドビュー事前計算+CONCURRENTLYリフレッシュ+ビューによるクエリ簡略化。

SQL
CREATE MATERIALIZED VIEW mv_sales_report AS
SELECT
  DATE_TRUNC('month', o.order_date)::date AS month,
  p.category,
  o.region,
  COUNT(*)                                AS order_count,
  COUNT(DISTINCT o.customer_id)           AS unique_customers,
  SUM(o.amount)                           AS total_revenue,
  SUM(o.amount) FILTER (WHERE o.amount >= 50000) AS big_deal_revenue,
  AVG(o.amount)                           AS avg_order_value,
  SUM(oi.quantity * oi.unit_price)        AS total_gmv
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
GROUP BY DATE_TRUNC('month', o.order_date), p.category, o.region
WITH DATA;

CREATE UNIQUE INDEX idx_mv_sales_report_pk
  ON mv_sales_report (month, category, region);

CREATE VIEW v_sales_dashboard AS
SELECT
  month,
  region,
  SUM(total_revenue)  AS region_revenue,
  SUM(order_count)    AS region_orders,
  SUM(unique_customers) AS region_customers
FROM mv_sales_report
GROUP BY month, region;

REFRESH MATERIALIZED VIEW CONCURRENTLY mv_sales_report;

9. 実行フロー

マテリアライズドビューの作成とリフレッシュの流れ。

100%
flowchart TD
    A[CREATE MATERIALIZED VIEW] --> B[基になるクエリを実行]
    B --> C[結果をディスクに書き込み]
    C --> D[インデックス作成可能]
    D --> E[クエリが直接ディスクデータを読み取り]
    E --> F{リフレッシュが必要?}
    F -->|REFRESH| G[基になるクエリを再実行]
    G --> H[旧データを置換]
    H --> E
    F -->|CONCURRENTLY| I[増分比較リフレッシュ]
    I --> J[読み取りをブロックしない]
    J --> E

    style A fill:#e1f5fe
    style E fill:#c8e6c9
    style I fill:#fff9c4
ステップ 説明
作成 基になるクエリを実行し、結果をディスクに永続化
クエリ ディスクデータを直接読み取り、基になるクエリは実行されない
リフレッシュ 基になるクエリを再実行し、旧データを置換
CONCURRENTLY 増分リフレッシュ、リフレッシュ中もクエリをブロックしない

❓ よくある質問

Q 通常のビューはパフォーマンスに影響しますか?
A いいえ。ビューはクエリの書き換えに過ぎず、パフォーマンスは基になるSQLを直接書くのと同じです。複雑なビューでも追加のオーバーヘッドはありません。
Q ビューを通じて挿入した後、挿入データが見つからないのはなぜですか?
A 挿入された行がビューのWHERE条件を満たしていない可能性があります。WITH CHECK OPTIONを使用してこれを防止してください。
Q CONCURRENTLYリフレッシュに一意インデックスが必要なの��なぜですか?
A CONCURRENTLYは新旧データを比較して増分リフレッシュを行います。一意インデックスが各行を識別します。ないとエラーになります。
Q マテリアライズドビューは直接のUPDATE/DELETEをサポートしますか?
A いいえ。マテリアライズドビューは読み取り専用です。REFRESHによってのみリフレッシュできます。変更するにはベーステーブルデータを更新してから再度REFRESHしてください。
Q WITH CASCADED CHECK OPTIONとWITH LOCAL CHECK OPTIONの違いは?
A CASCADEDは現在のビューとすべての依存ビューの条件をチェックします。LOCALは現在のビューの条件のみチェックします。ネストビューではCASCADEDの方が安全です。
Q マテリアライズドビューを別のマテリアライズドビューの上に構築できますか?
A はい。PostgreSQLはネストされたマテリアライズドビューをサポートしますが、依存順序に従って手動でリフレッシュする必要があります。

📖 まとめ


📝 練習問題

  1. ⭐ 過去30日間の注文をクエリするビューv_recent_ordersを作成し、customer_nameproduct_nameを含めてください。
  2. ⭐ ステータス = 'active'の顧客のみを表示する更新可能ビューv_active_customersを作成し、WITH CHECK OPTIONを追加してください。
  3. ⭐⭐ 日次・カテゴリ別の売上を集計するマテリアライズドビューmv_daily_category_salesを作成し、CONCURRENTLYリフレッシュをサポートするための一意インデックスを追加してください。
  4. ⭐⭐ ネストビュー構成を設計してください。マテリアライズドビューが詳細データを事前計算し、通常のビューがマテリアライズドビューの上で次元集計を行います。
  5. ⭐⭐⭐ 毎日午前3時にmv_daily_category_salesをリフレッシュし、リフレッシュに5分以上かかる場合にアラートをログ記録するpg_cronスケジュールジョブを作成してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%