Back to Stored Procedures, Views & Triggers
Q5Question 5 of 6

Plain View vs Materialized View: and When to Use Each

Key Concepts
  • A plain view stores only its SELECT definition and re-executes it on every access, so results are always fresh.
  • A materialized view physically stores the query result as a snapshot, giving fast reads but potentially stale data.
  • Materialized views must be refreshed: fully, incrementally, or with CONCURRENTLY to avoid blocking reads.
  • Use a plain view when data must be current, the query is cheap, or writes are frequent relative to reads.
  • Use a materialized view when the query is expensive, reads vastly outnumber writes, and slight staleness is acceptable.
  • Oracle and PostgreSQL have native materialized views; SQL Server uses indexed views; MySQL has no native support.
  • The core trade-off is freshness versus read speed: plain views optimize for correctness, materialized views for latency.

Definition

A plain view recomputes live every access (fresh but slower); a materialized view stores a snapshot (fast but can be stale and needs refreshing).
  • Plain view – a stored SELECT definition executed on every query, holding no data and always reflecting current base tables.
  • Materialized view – a physical, stored snapshot of the query result, served quickly from disk but potentially stale until refreshed.
  • Refresh modes – full refresh re-runs the entire query; incremental or fast refresh applies only deltas; CONCURRENTLY (PostgreSQL) avoids blocking reads during refresh.
  • Storage – plain views use zero extra disk; materialized views consume disk proportional to the result set.
  • Engine support – Oracle and PostgreSQL offer native materialized views; SQL Server achieves similar results with indexed views; MySQL has no native support.

A plain view is like asking the National Bank of India clerk to recompute "total deposits per branch today" every single time you ask. It is always current, but you wait while they add it up. A materialized view is like a printed end-of-hour summary pinned to the wall: you read it instantly, but it only shows what was true when it was last printed, so it is fast yet possibly out of date until it is reprinted.

Understanding the Concepts

What is a plain view?

A plain view stores only its query text. When you SELECT from it, the database expands the view into your query and runs it against live base tables. No extra storage is used and results are always consistent with the current data.

SQL
-- Plain view: always fresh, computed on every access
CREATE VIEW branch_deposit_totals AS
SELECT
    b.branch_id,
    b.branch_name,
    SUM(a.balance) AS total_deposits
FROM branches b
JOIN accounts a ON b.branch_id = a.branch_id
WHERE a.status = 'ACTIVE'
GROUP BY b.branch_id, b.branch_name;

-- Every query re-runs the aggregation
SELECT * FROM branch_deposit_totals
WHERE branch_name = 'DELHI';

What is a materialized view?

A materialized view executes its query once and stores the resulting rows physically on disk, like a cached table. Reads hit the stored snapshot directly and are fast.

SQL
-- Materialized view: stored snapshot, fast reads
CREATE MATERIALIZED VIEW mv_branch_deposit_totals AS
SELECT
    b.branch_id,
    b.branch_name,
    SUM(a.balance) AS total_deposits
FROM branches b
JOIN accounts a ON b.branch_id = a.branch_id
WHERE a.status = 'ACTIVE'
GROUP BY b.branch_id, b.branch_name;

-- Reads hit stored data, no re-aggregation
SELECT * FROM mv_branch_deposit_totals
WHERE branch_name = 'DELHI';

How does refreshing a materialized view work?

The snapshot does not automatically track base-table changes. You must refresh it explicitly. PostgreSQL supports full refresh and concurrent refresh. Oracle also supports incremental (fast) refresh that applies only the deltas.

SQL
-- Full refresh: re-runs the entire query, blocks reads briefly
REFRESH MATERIALIZED VIEW mv_branch_deposit_totals;

-- Concurrent refresh (PostgreSQL): no read blocking, requires a unique index
CREATE UNIQUE INDEX idx_mv_branch ON mv_branch_deposit_totals(branch_id);
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_branch_deposit_totals;
JAVA
package com.bank.service;

import java.sql.Connection;
import java.sql.Statement;
import java.util.logging.Logger;

public class MaterializedViewRefreshService {

    private static final Logger LOG = Logger.getLogger(
        MaterializedViewRefreshService.class.getName());

    private final Connection connection;

    public MaterializedViewRefreshService(Connection connection) {
        this.connection = connection;
    }

    public void refreshBranchTotals() {
        String sql = "REFRESH MATERIALIZED VIEW CONCURRENTLY mv_branch_deposit_totals";
        try (Statement stmt = connection.createStatement()) {
            long start = System.currentTimeMillis();
            stmt.execute(sql);
            long elapsed = System.currentTimeMillis() - start;
            LOG.info("Refreshed mv_branch_deposit_totals in " + elapsed + " ms");
        } catch (Exception e) {
            LOG.severe("Refresh failed: " + e.getMessage());
        }
    }
}

How is a plain view different from a materialized view?

| Aspect | Plain View | Materialized View |
|---|---|---|
| Storage | None (query text only) | Physical rows on disk |
| Freshness | Always current | Stale until refreshed |
| Read speed | Re-runs query each time | Reads stored snapshot |
| Write overhead | None | Refresh cost |
| Indexing | Uses base-table indexes | Can have its own indexes |
| Engine support | All SQL databases | Oracle, PostgreSQL, SQL Server (indexed views) |

SQL
-- Plain view: always current but slow on 100M rows
SELECT * FROM branch_deposit_totals;  -- re-aggregates every call

-- Materialized view: fast but may be 15 minutes stale
SELECT * FROM mv_branch_deposit_totals;  -- reads cached result

When should you use a plain view?

Use a plain view when the data must always be current, the underlying query is cheap, writes are frequent relative to reads, or you simply need simplification, security, and abstraction without extra storage.

SQL
-- Compliance screen: balance must never be stale
CREATE VIEW compliance_account_status AS
SELECT account_id, holder_name, balance, status, kyc_verified
FROM accounts
WHERE kyc_verified = FALSE;

-- Always shows the real-time list of unverified accounts
SELECT * FROM compliance_account_status;

When should you use a materialized view?

Use a materialized view when the query is expensive, reads vastly outnumber writes, and the business can accept data that is accurate as of the last refresh. Classic cases include dashboards, nightly analytics, and precomputed aggregates.

SQL
-- Executive dashboard: reads thousands of times per hour,
-- aggregation over millions of rows, 15-minute staleness acceptable
CREATE MATERIALIZED VIEW mv_executive_dashboard AS
SELECT
    b.branch_name,
    a.account_type,
    COUNT(*) AS account_count,
    SUM(a.balance) AS total_balance,
    AVG(a.balance) AS avg_balance
FROM accounts a
JOIN branches b ON a.branch_id = b.branch_id
WHERE a.status = 'ACTIVE'
GROUP BY b.branch_name, a.account_type;

-- Schedule refresh every 15 minutes via cron or pg_cron
-- SELECT cron.schedule('*/15 * * * *',
--   'REFRESH MATERIALIZED VIEW CONCURRENTLY mv_executive_dashboard');

What about MySQL which has no native materialized view?

MySQL does not have native materialized views. Teams simulate one with a summary table and a refresh job.

SQL
-- MySQL: simulate a materialized view with a summary table
CREATE TABLE summary_branch_deposits (
    branch_id    INT PRIMARY KEY,
    branch_name  VARCHAR(100),
    total_balance DECIMAL(18,2),
    refreshed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Refresh procedure
DELIMITER //
CREATE PROCEDURE refresh_branch_deposits()
BEGIN
    TRUNCATE TABLE summary_branch_deposits;
    INSERT INTO summary_branch_deposits (branch_id, branch_name, total_balance)
    SELECT b.branch_id, b.branch_name, SUM(a.balance)
    FROM branches b
    JOIN accounts a ON b.branch_id = a.branch_id
    WHERE a.status = 'ACTIVE'
    GROUP BY b.branch_id, b.branch_name;
END //
DELIMITER ;

CALL refresh_branch_deposits();

What are common mistakes with materialized views?

The most common mistake is forgetting to schedule refreshes, so the dashboard silently shows stale data for days. Another mistake is choosing full refresh on a huge view without a unique index, blocking reads during the refresh.

SQL
-- Wrong: full refresh blocks reads on a large materialized view
REFRESH MATERIALIZED VIEW mv_executive_dashboard;  -- blocks readers

-- Correct: add a unique index and use CONCURRENTLY
CREATE UNIQUE INDEX idx_mv_exec ON mv_executive_dashboard(branch_name, account_type);
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_executive_dashboard;  -- no read block

What should a senior engineer know?

A senior engineer should frame the decision as a trade-off between freshness and read speed. They should know that the refresh cadence is itself a design decision: too frequent wastes I/O, too infrequent serves stale data. They should also know that Oracle supports incremental refresh using materialized view logs, which is far cheaper than a full re-run on large datasets.

SQL
-- Oracle: incremental (fast) refresh with a materialized view log
CREATE MATERIALIZED VIEW LOG ON accounts
WITH ROWID, PRIMARY KEY (account_id, branch_id, balance)
INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW mv_branch_totals
BUILD IMMEDIATE
REFRESH FAST ON DEMAND
AS
SELECT branch_id, SUM(balance) AS total_balance
FROM accounts
GROUP BY branch_id;

-- Fast refresh applies only the changes since the last refresh
EXEC DBMS_MVIEW.REFRESH('mv_branch_totals', 'F');

Visual Overview

DIAGRAM
┌─────────────────────────────────────────────────┐
│         Plain View vs Materialized View         │
├─────────────────────────────────────────────────┤
│                                                 │
│  Plain View                                     │
│  ┌──────────┐    SELECT    ┌──────────────┐     │
│  │  Caller  │ ──────────►  │ View Query   │     │
│  └──────────┘              │ (re-executes │     │
│       ▲                    │  every time) │     │
│       │  results           └──────┬───────┘     │
│       └────────────────────────────┘             │
│       Always fresh, no storage                  │
│                                                 │
│  Materialized View                              │
│  ┌──────────┐    SELECT    ┌──────────────┐     │
│  │  Caller  │ ──────────►  │ Stored       │     │
│  └──────────┘              │ Snapshot     │     │
│       ▲                    └──────────────┘     │
│       │  fast read              ▲               │
│       └─────────────────────────┘               │
│                                                 │
│  ┌──────────────┐  REFRESH  ┌──────────────┐    │
│  │ Base Tables  │ ────────► │ Snapshot     │    │
│  └──────────────┘  (manual/ │ (updated)    │    │
│                    scheduled)└──────────────┘    │
│       Fast reads, stale until refreshed         │
└─────────────────────────────────────────────────┘

Key Takeaway

A plain view and a materialized view trade freshness against read speed. A plain view stores only its SELECT definition and executes it every time it is queried, so it holds no data and always reflects the current base tables, but it pays the full cost of its underlying query on every access. A materialized view executes its query once and physically stores the resulting rows as a snapshot, so reads are fast, but the snapshot can be stale and must be refreshed.

The decision rule is based on the read-to-write ratio and the tolerance for staleness. Use a plain view when the data must always be current, the underlying query is cheap, or writes are frequent relative to reads. Use a materialized view when the query is expensive, reads vastly outnumber writes, and the business can accept data that is accurate as of the last refresh.

At the National Bank of India, the compliance screen used a plain view so an account's current balance was never stale, while the executive dashboard used a materialized view of total deposits per branch over millions of transactions, refreshed every fifteen minutes, because that aggregation was read thousands of times an hour and a short lag was acceptable. Pankaj also noted that engine support differs: Oracle and PostgreSQL have native materialized views, SQL Server uses indexed views, and MySQL has none, so the team simulated one with a summary table and a refresh procedure.

The senior nuance is that choosing the refresh cadence is itself a design decision balancing freshness against refresh cost, and that Oracle's incremental refresh with materialized view logs is far cheaper than a full re-run on large datasets.

Remember one sentence: a plain view recomputes live and is always fresh but slower, while a materialized view serves a stored snapshot that is fast but can be stale and must be refreshed.

---

Share
Use ← → arrow keys to navigate