Бран · продюсер «Хроник Пограничья»
У меня три отчёта про выручку, и все показывают разное. Маркетинг считает так, ты иначе, финансы третьим способом. Можно сделать так, чтобы «выручка» значила одно и то же везде?
Можно, и это одна из главных задач аналитика после первых месяцев работы. С босса блока C мы каждый раз заново пишем одно и то же заклинание: только paid, одна покупка на чек, без тестовых аккаунтов студии. Каждый раз можно ошибиться, и у каждого коллеги заклинание чуть своё. Сегодня записываем правило один раз и даём ему имя.
Окно и фотография
Представь, что в стене архива прорубили окно. Через него видно полки, но только те, что лицом к окну, и только в нужном ракурсе. Полки меняются, окно показывает их сегодняшнее состояние. Представление работает так же: это запрос, сохранённый под именем. Читаешь его как таблицу, а база каждый раз выполняет запрос заново, поэтому данные всегда свежие.
CREATE VIEW clean_purchases AS
SELECT p.*
FROM purchases p
WHERE p.status = 'paid'
AND p.id IN (SELECT min(id) FROM purchases GROUP BY receipt_id)
AND p.player_id NOT IN (SELECT id FROM players WHERE email LIKE '%@studio.dev');
Теперь отчёт пишется коротко и одинаково у всех:
SELECT count(*), sum(amount_rub) FROM clean_purchases;
1139 покупок на 657 756 ₽ вместо сырых 1250 на 726 433 ₽. Разница это покупки тестировщиков из урока про чистку строк, дубли вебхуков и возвраты из урока про дубли. Теперь их не надо помнить: правило живёт в одном месте. Как устроены представления с точки зрения разработчика, рассказывает урок про VIEW.
Фотография вместо окна
Окно всегда свежее, но за каждый взгляд база заново выполняет весь запрос. Если отчёт тяжёлый, а смотрят на него сто раз в день, выгоднее один раз сфотографировать результат. Материализованное представление это фотография: результат сохранён, читается быстро, но сам не меняется.
CREATE MATERIALIZED VIEW daily_revenue AS
SELECT d::date AS day,
count(c.id) AS purchases,
coalesce(sum(c.amount_rub), 0) AS revenue
FROM generate_series(date '2026-01-05', date '2026-06-30', interval '1 day') AS d
LEFT JOIN clean_purchases c ON c.created_at::date = d::date
GROUP BY 1;
Это уже витрина: таблица под конкретные отчёты, одна строка на день, все 177 дней от запуска игры, включая 3 дня без покупок. Календарь из generate_series мы строили в уроке про дыры в датах. Дашборд выручки читает 177 готовых строк вместо того, чтобы каждый раз чистить покупки.
Цена фотографии в свежести. Добавим покупку за 30 июня на 99 ₽: clean_purchases сразу покажет 2093 ₽ за день, а daily_revenue по-прежнему 1994 ₽. Только после REFRESH MATERIALIZED VIEW daily_revenue снимок догонит данные. В рабочих базах обновление ставят по расписанию, например каждую ночь, и на дашборде пишут, когда данные обновлялись.
урок из курса
Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.