Overview
A materialized view stores the result of a SELECT on disk like a table, while a regular view reruns its query on every access. Create one when a dashboard or report reruns the same expensive aggregation and can tolerate minutes of staleness. The data stays stale until REFRESH MATERIALIZED VIEW.
Rules
- Schedule refreshes (cron,
pg_cron, or a job) or run one after bulk loads. REFRESH ... CONCURRENTLYkeeps the view readable during the refresh. It needs an already populated view and at least one unique index on plain columns covering all rows (noWHERE, no expressions), allows only one refresh at a time, and can be slower than a blocking refresh when most rows change.- PostgreSQL has no built-in incremental refresh as of version 18: every refresh recomputes the whole query. The
pg_ivmextension adds incremental maintenance. - Index the view like a table and confirm use with
EXPLAIN.
CREATE MATERIALIZED VIEW monthly_revenue AS
SELECT date_trunc('month', created_at) AS month, sum(amount) AS revenue
FROM orders GROUP BY 1;
CREATE UNIQUE INDEX monthly_revenue_month_idx ON monthly_revenue (month);
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_revenue;Related
- secondary-index: indexes make the view fast and enable concurrent refresh.
- read-replica: offloads live reads rather than precomputing.
- base: stale reads by design.
- postgres: refresh strategies.
- postgres-explain: confirm the view is hit.