Materialized Views
A regular view stores no data of its own — it re-runs its query every time it is accessed. A materialized view flips that trade-off: it physically stores the result set of its query, just like a real table, and only updates that stored data when explicitly told to. This makes reading from a materialized view very fast, at the cost of the data potentially being out of date until the next refresh.
Creating and refreshing a materialized view
In PostgreSQL, a materialized view is created with CREATE MATERIALIZED VIEW and updated on demand with REFRESH MATERIALIZED VIEW.
Creating a materialized view for an expensive report
CREATE MATERIALIZED VIEW daily_sales_summary 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;
Querying it is instant, because the aggregation has already been computed and stored — the database is just reading rows, not recalculating SUM and AVG across the whole orders table.
Reading from the materialized view
SELECT * FROM daily_sales_summary WHERE order_date = CURRENT_DATE - 1;
To bring the stored data back in sync with the underlying tables, the materialized view must be refreshed explicitly:
Refreshing the stored result set
REFRESH MATERIALIZED VIEW daily_sales_summary;
REFRESH MATERIALIZED VIEW
When to reach for a materialized view
Materialized views shine for expensive, repeatedly-run aggregate queries where perfectly real-time data is not required. A classic example is a daily sales dashboard: recomputing revenue-by-region-by-day across millions of order rows on every page load would be slow and wasteful, but refreshing that summary once, on a nightly schedule, and letting the dashboard read the pre-computed result is fast and perfectly acceptable, since the dashboard is not expected to show data from the last few minutes anyway.
PostgreSQL supports REFRESH MATERIALIZED VIEW CONCURRENTLY
A plain REFRESH locks the materialized view against reads while it rebuilds. PostgreSQL also offers REFRESH MATERIALIZED VIEW CONCURRENTLY, which rebuilds the data in the background and swaps it in without blocking concurrent SELECT queries — at the cost of requiring a unique index on the materialized view first.
A non-blocking refresh
CREATE UNIQUE INDEX ON daily_sales_summary (order_date, region); REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales_summary;
A materialized view physically stores its query result, unlike a regular view which stores nothing.
It must be explicitly refreshed (REFRESH MATERIALIZED VIEW in PostgreSQL) to pick up changes in the underlying tables.
The data can be stale between refreshes — this is an intentional trade-off for fast reads.
Best suited for expensive aggregate reports/dashboards that do not need real-time freshness.
MySQL has no native materialized views and typically simulates them with a real table plus a scheduled refresh job.