Materialized Views
SELECT query — but unlike a regular view it physically stores the query result on disk at the moment it's created. Reading from it afterwards just reads the stored rows; it does not re-run the underlying query.Creating a materialized view
Create and populate
CREATE MATERIALIZED VIEW monthly_sales_summary AS
SELECT
date_trunc('month', order_date) AS month,
region,
SUM(total) AS revenue,
COUNT(*) AS order_count
FROM orders
GROUP BY 1, 2;SELECT * FROM monthly_sales_summary is just a plain table scan — no joins, no aggregation, no recomputation.Refreshing the data
Because the data is a snapshot, it does not automatically track changes to the underlying tables. New orders inserted after the materialized view was created simply won't appear until it is explicitly refreshed:
REFRESH MATERIALIZED VIEW monthly_sales_summary;
Refreshing without blocking readers
REFRESH MATERIALIZED VIEW takes an exclusive lock on the view for the duration of the refresh — anyone trying to SELECT from it has to wait until the refresh finishes. For a large or frequently-queried materialized view, that pause can be disruptive. CONCURRENTLY avoids it:REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales_summary;
REFRESH ... CONCURRENTLY works by computing the new result set alongside the old one and diffing them, which requires PostgreSQL to identify individual rows uniquely. Because of that, it only works if the materialized view has at least one UNIQUE index on it:CREATE UNIQUE INDEX monthly_sales_summary_month_region_idx ON monthly_sales_summary (month, region); REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales_summary;
Views vs. materialized views
View | Materialized View | |
|---|---|---|
Stores data? | No — re-runs the query each time | Yes — stores the result on disk |
Freshness | Always current | Stale until refreshed |
Read cost | Cost of the full underlying query | Cost of a plain table scan |
Refresh needed? | No | Yes, via |
When to reach for one
Materialized views shine for expensive aggregate reports and dashboards where the underlying data doesn't need to be real-time — a daily sales summary, a monthly active users count, a precomputed leaderboard. Refresh it on a schedule (a cron job, a scheduled task runner, or a trigger-based mechanism) that matches how fresh the data actually needs to be, and let every read against it be fast.
CREATE MATERIALIZED VIEW ... AS SELECT ...computes and stores the result immediately.REFRESH MATERIALIZED VIEWrecomputes the stored result — data is stale until you run it.REFRESH MATERIALIZED VIEW CONCURRENTLYavoids locking out readers, but requires a unique index on the view.Best suited to expensive, infrequently-changing aggregates rather than real-time data.