Звичайне подання (view) - збережений запит: щоразу при зверненні він виконується заново.
Матеріалізоване подання зберігає результат запиту як таблицю. Читати його так само швидко, як таблицю, - але дані оновлюються лише за командою.
CREATE MATERIALIZED VIEW sales_by_month AS
SELECT date_trunc('month', created_at) AS month,
product_id,
sum(total) AS revenue,
count(*) AS orders
FROM orders
WHERE status = 'paid'
GROUP BY 1, 2;
CREATE UNIQUE INDEX sales_by_month_key ON sales_by_month (month, product_id);
Оновлення:
REFRESH MATERIALIZED VIEW sales_by_month; -- блокує читання на весь час оновлення
REFRESH MATERIALIZED VIEW CONCURRENTLY sales_by_month; -- читання не блокується
CONCURRENTLY будує новий результат поруч, порівнює зі старим і застосовує різницю. Вимагає унікального індексу на поданні й працює довше, зате дашборди, що читають подання, не зависають.
Коли доречно:
- Важкі агрегати для дашбордів і звітів, де допустимі дані «станом на кілька хвилин чи годин тому».
- Денормалізовані дані для пошуку чи API, які дорого збирати з багатьох таблиць на кожен запит.
Обмеження:
- Немає автоматичного оновлення - лише
REFRESHза розкладом (cron, планувальник Laravel) чи за подією. - Оновлюється цілком: навіть при зміні одного рядка запит перераховується повністю. Для дуже великих агрегатів - інкрементальні підходи (окрема таблиця з оновленням за тригером чи пакетними задачами, розширення на кшталт
pg_ivm). - Займає місце, як звичайна таблиця, і потребує індексів під свої запити.
Альтернатива на рівні застосунку - кеш результату запиту (Redis). Матеріалізоване подання краще, коли результат великий, його фільтрують і з'єднують з іншими таблицями прямо в SQL.