Materialized Views
Also Known As
materialised views
precomputed views
cached query result
TL;DR
Pre-computed query results stored as a real table — dramatically faster reads for expensive aggregations at the cost of periodic refresh overhead.
Explanation
A materialized view stores the result of a complex query (heavy aggregations, multi-table joins) as a physical table that can be indexed and queried instantly. Unlike regular views which recompute on every access, materialized views are refreshed on a schedule or on-demand. PostgreSQL supports REFRESH MATERIALIZED VIEW CONCURRENTLY (non-blocking). MySQL lacks native materialized views — they are emulated as real tables populated by event schedulers or application code. Ideal for dashboards and reporting that aggregate millions of rows but can tolerate slightly stale data (last-refreshed 5 minutes ago).
Common Misconception
✗ Materialized views are always up to date like regular views. Regular views execute the query on each access. Materialized views store the result physically and must be explicitly refreshed — they trade freshness for read performance, making them suitable for reports, not real-time data.
Why It Matters
Materialized views pre-compute and store expensive query results — complex aggregations that take seconds to run become instant reads, at the cost of data that may be slightly stale.
Common Mistakes
- Not refreshing materialized views — stale data silently serves outdated results.
- Refreshing too frequently — REFRESH MATERIALIZED VIEW locks the view briefly; concurrent refresh avoids this.
- Using materialized views for frequently changing data where staleness is unacceptable.
- Not indexing materialized views — they are stored like tables and need indexes for fast access.
Code Examples
✗ Vulnerable
-- Slow query run on every dashboard load:
SELECT category, COUNT(*), SUM(revenue) FROM orders
JOIN products ON orders.product_id = products.id
GROUP BY category; -- 3 seconds on 10M rows
-- Materialized view — pre-computed, instant:
CREATE MATERIALIZED VIEW order_stats AS (above query);
REFRESH MATERIALIZED VIEW CONCURRENTLY order_stats; -- Run via cron
✓ Fixed
-- PostgreSQL: pre-compute expensive aggregate
CREATE MATERIALIZED VIEW monthly_revenue AS
SELECT
DATE_TRUNC('month', created_at) AS month,
SUM(total) AS revenue,
COUNT(*) AS order_count
FROM orders
WHERE status = 'paid'
GROUP BY 1;
CREATE INDEX ON monthly_revenue(month);
-- Query is now instant (reads pre-computed data)
SELECT * FROM monthly_revenue WHERE month >= '2024-01-01';
-- Refresh (schedule in cron or trigger)
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_revenue;
-- CONCURRENTLY allows reads during refresh (PostgreSQL 9.4+)
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
15 Mar 2026
Edited
22 Mar 2026
Views
15
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 0
No pings yet today
No pings yesterday
Perplexity 5
Unknown AI 2
Majestic 1
Google 1
Ahrefs 1
Also referenced
How they use it
crawler 10
Related categories
⚡
DEV INTEL
Tools & Severity
🟡 Medium
⚙ Fix effort: Medium
⚡ Quick Fix
Create a materialised view for any reporting query taking >1 second — schedule REFRESH MATERIALIZED VIEW CONCURRENTLY during off-peak hours to serve dashboards instantly from pre-computed data
📦 Applies To
PHP 5.0+
web
cli
🔗 Prerequisites
🔍 Detection Hints
Dashboard recalculating complex aggregation on every request; report query joining 5+ tables; same GROUP BY with COUNT SUM repeated everywhere
Auto-detectable:
✗ No
pganalyze
mysql-slow-query-log
blackfire
⚠ Related Problems
🤖 AI Agent
Confidence: Medium
False Positives: Medium
✗ Manual fix
Fix: Medium
Context: File