A materialized view stores the result of a query on disk, letting you query a pre-computed snapshot that you can manually or automatically refresh.
A materialized view caches a query result as a physical table, cutting execution time for expensive joins or aggregations. Unlike a normal view, the data is fixed until you refresh it.
Use one when reporting queries hit large fact tables, dashboards need sub-second latency, or you want to isolate analytical workloads from OLTP traffic.
Run CREATE MATERIALIZED VIEW
with a SQL SELECT. Include WITH DATA
to populate immediately or WITH NO DATA
to defer.
CREATE MATERIALIZED VIEW customer_lifetime_value AS
SELECT c.id, c.name,
SUM(o.total_amount) AS lifetime_value,
COUNT(o.id) AS orders_count
FROM Customers c
JOIN Orders o ON o.customer_id = c.id
GROUP BY c.id, c.name;
Run REFRESH MATERIALIZED VIEW
. Add CONCURRENTLY
to avoid blocking reads; index the view first to enable it.
REFRESH MATERIALIZED VIEW CONCURRENTLY customer_lifetime_value;
Yes. Schedule REFRESH MATERIALIZED VIEW
in cron
, PgAgent, or a managed scheduler so dashboards stay current without manual effort.
Index the columns you query most. Add WITH NO DATA
in migrations to shorten deploys, then populate off-peak. Combine multiple small queries into one view when possible.
Missing indexes: Without indexes on the materialized view, refreshes with CONCURRENTLY
fail. Create indexes first.
Over-refreshing: Refreshing every minute on a low-change dataset wastes I/O. Match schedule to business need.
CREATE MATERIALIZED VIEW latest_product_stock AS
SELECT id, name, price, stock
FROM Products
WHERE stock > 0;
-- Later
REFRESH MATERIALIZED VIEW latest_product_stock;
They occupy disk space, can become stale, and require refresh logic. For lightweight or rapidly changing queries, use normal views instead.
Yes, a normal refresh takes an exclusive lock, blocking reads. Use CONCURRENTLY to keep the view available, but ensure it has a unique index first.
No. The view is read-only. To change data, update the base tables and then refresh the view.
Track the last refresh time in an audit table or query pg_catalog.pg_matviews
. Combine with trigger-based flags if freshness is critical.