CREATE MATERIALIZED VIEW customer_stats ASSELECT customer_id, count(*) AS orders, sum(total_cents) AS revenue FROM orders GROUP BY customer_idWITH DATA;
2. Creating with Data
CREATE MATERIALIZED VIEW v WITH (fillfactor = 100) AS SELECT ... WITH DATA;
3. Creating without Data
CREATE MATERIALIZED VIEW v AS SELECT ... WITH NO DATA;REFRESH MATERIALIZED VIEW v;
Mode
Behavior
WITH DATA
Populate immediately
WITH NO DATA
Empty (unqueryable until refresh)
4. Querying Materialized Views
SELECT * FROM customer_stats WHERE orders > 10;
Note: Reads are normal table reads—use indexes on materialized views for speed.
5. Refreshing Materialized View
REFRESH MATERIALIZED VIEW customer_stats;
Lock
Effect
Default
ACCESS EXCLUSIVE — blocks reads
CONCURRENTLY
Allows reads but requires unique index
6. Refreshing Concurrently
CREATE UNIQUE INDEX customer_stats_pk ON customer_stats(customer_id);REFRESH MATERIALIZED VIEW CONCURRENTLY customer_stats;
7. Creating Indexes on Materialized Views
CREATE INDEX customer_stats_rev ON customer_stats(revenue DESC);
8. Listing Materialized Views
\dmSELECT matviewname, ispopulated, definition FROM pg_matviews;
9. Dropping Materialized View
DROP MATERIALIZED VIEW IF EXISTS customer_stats;
10. Understanding Materialized View Use Cases
Use Case
Why
Heavy reporting queries
Avoid recompute on every dashboard hit
Pre-joined dimension tables
Avoid expensive JOINs
Caching FDW results
Snapshot remote data locally
Stale-OK aggregates
Refresh nightly via cron
Warning: Data is stale until next refresh; never use for transactional reads. Use logical replication or triggers for near-real-time needs.