VIEW в SQL: шпаргалка для аналитика
Зачем аналитику VIEW
VIEW — сохранённый SELECT, которым можно пользоваться как таблицей. В аналитике это основной инструмент для:
- Обёртки сложных запросов в читабельные «таблицы».
- Ограничения доступа (разные VIEW для разных ролей).
- Унификации логики в команде — все считают retention одинаково.
Базовый синтаксис
CREATE VIEW active_users AS
SELECT user_id, name, last_login
FROM users
WHERE status = 'active';
-- Использование
SELECT * FROM active_users;
SELECT COUNT(*) FROM active_users WHERE last_login > CURRENT_DATE - INTERVAL '7 day';View «прозрачна»: каждый раз, когда вы её запрашиваете, СУБД выполняет подлежащий запрос.
CREATE OR REPLACE VIEW
CREATE OR REPLACE VIEW active_users AS
SELECT user_id, name, last_login, email
FROM users WHERE status = 'active';Для обновления структуры — удобнее, чем DROP + CREATE.
DROP VIEW
DROP VIEW IF EXISTS active_users;
-- Каскадно удалить зависимые view
DROP VIEW active_users CASCADE;Updatable VIEW
Простые VIEW можно обновлять:
CREATE VIEW active_users AS SELECT * FROM users WHERE status = 'active';
UPDATE active_users SET last_login = NOW() WHERE user_id = 1;
-- Обновляет users, не active_usersНо только если в VIEW нет: JOIN, GROUP BY, DISTINCT, оконных функций. Иначе UPDATE не работает.
Если хочется сразу закрепить тему на практике — открой тренажёр в Telegram. 10 минут в день — и синтаксис в пальцах.
WITH CHECK OPTION
Запрещает вставку/обновление, которые нарушат условия VIEW:
CREATE VIEW active_users AS
SELECT * FROM users WHERE status = 'active'
WITH CHECK OPTION;
-- ❌ Будет ошибка — вставка status='inactive' нарушает условие
INSERT INTO active_users (name, status) VALUES ('X', 'inactive');Materialized View
Обычная VIEW — это «запрос на лету». Materialized VIEW — сохранённый результат:
CREATE MATERIALIZED VIEW monthly_stats AS
SELECT DATE_TRUNC('month', created_at) AS month, SUM(amount) AS rev
FROM orders GROUP BY 1;
-- Использование как обычная таблица (быстро)
SELECT * FROM monthly_stats;
-- Обновить данные (принудительно)
REFRESH MATERIALIZED VIEW monthly_stats;
-- Concurrent refresh (без блокировки читателей)
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_stats;Плюсы: быстрое чтение (данные физически хранятся).
Минусы: данные не свежие — требуется REFRESH. Обычно делают по расписанию (cron, pg_cron, Airflow).
VIEW vs CTE vs Temp Table
| Критерий | VIEW | CTE | Temp table |
|---|---|---|---|
| Область видимости | БД (постоянно) | один запрос | сессия |
| Переиспользование | ✅ во всех запросах | только в текущем | только в сессии |
| Производительность | выполняется каждый раз | inline обычно | может иметь индексы |
| Сохраняет данные | ❌ (обычная VIEW) | ❌ | ✅ |
Вложенные VIEW
CREATE VIEW customer_stats AS
SELECT user_id, COUNT(*) AS orders, SUM(amount) AS revenue
FROM orders GROUP BY user_id;
CREATE VIEW top_customers AS
SELECT * FROM customer_stats WHERE revenue > 10000;Можно — но осторожно. Вложения 3+ уровней делают запросы медленными и сложными в отладке.
Типовые применения
1. Унификация расчёта метрик
CREATE VIEW retention_daily AS
SELECT
cohort_date,
COUNT(*) AS cohort_size,
COUNT(*) FILTER (WHERE days_since_signup = 7) * 1.0 / COUNT(*) AS d7
FROM user_activity GROUP BY cohort_date;Все в команде считают retention одинаково, через один VIEW.
2. Ограничение доступа
CREATE VIEW public_users AS
SELECT id, name, city FROM users; -- без email, phone
GRANT SELECT ON public_users TO role_external;3. Упрощение сложных запросов
CREATE VIEW customer_stats AS
SELECT u.user_id, u.name, u.city,
COUNT(o.order_id) AS orders,
SUM(o.amount) AS revenue
FROM users u LEFT JOIN orders o USING (user_id)
GROUP BY u.user_id, u.name, u.city;Теперь команда вместо 10-строчного JOIN пишет SELECT * FROM customer_stats.
Чтобы не только читать теорию, но и решать реальные задачи — загляните в бот Карьерника. Там по каждой теме подборка вопросов с разборами.
Когда VIEW — плохая идея
1. Очень сложная логика с join 5+ таблиц
Оптимизатор может не справиться, VIEW будет тормозить. Вынести в CREATE TABLE AS SELECT (snapshot) или dbt-модель.
2. Часто меняющаяся структура
Переименовать столбец — ломается VIEW, ломается weekly-отчёт, ломается кто-то из команды. Документируйте дependencies.
3. Для критичной производительности
Обычная VIEW — каждый запрос заново. Если нужно быстро — materialized view с расписанным REFRESH.
10 практических задач
1. VIEW активных пользователей
CREATE VIEW active_users AS
SELECT * FROM users WHERE status = 'active';2. VIEW суммарной статистики по клиенту
CREATE VIEW customer_summary AS
SELECT user_id, COUNT(order_id) AS orders, SUM(amount) AS revenue,
MIN(created_at) AS first_order, MAX(created_at) AS last_order
FROM orders GROUP BY user_id;3. VIEW скрывающая персональные данные
CREATE VIEW users_public AS
SELECT id, name, city, created_at FROM users;4. MATERIALIZED VIEW для дневной статистики
CREATE MATERIALIZED VIEW daily_gmv AS
SELECT created_at::DATE AS day, SUM(amount) AS gmv
FROM orders GROUP BY 1;
-- Обновлять каждую ночь через cron
REFRESH MATERIALIZED VIEW daily_gmv;5. Обновление View после изменения структуры
CREATE OR REPLACE VIEW customer_summary AS
SELECT user_id, COUNT(order_id) AS orders,
SUM(amount) AS revenue,
AVG(amount) AS aov -- новое
FROM orders GROUP BY user_id;6. VIEW с параметрами через FUNCTION (PostgreSQL)
CREATE FUNCTION get_user_orders(uid INT)
RETURNS TABLE(order_id INT, amount DECIMAL) AS $$
SELECT order_id, amount FROM orders WHERE user_id = uid;
$$ LANGUAGE SQL;
SELECT * FROM get_user_orders(1);7. VIEW для retention
CREATE VIEW user_retention AS
SELECT user_id,
(MIN(event_time)::DATE) AS signup_date,
(MAX(event_time)::DATE - MIN(event_time)::DATE) AS days_active
FROM events GROUP BY user_id;8. Зависимости VIEW
-- Найти, какие VIEW зависят от таблицы users
SELECT viewname FROM pg_views WHERE definition LIKE '%users%';9. Дроп с каскадом
DROP VIEW customer_summary CASCADE;
-- Удалит все зависимые VIEW10. VIEW как «секьюрити слой»
-- Менеджер видит только заказы своего региона
CREATE VIEW regional_orders AS
SELECT * FROM orders
WHERE region = current_setting('app.current_region');Как тренироваться
VIEW — не учится отдельно. Используйте в реальных проектах: когда замечаете, что один и тот же JOIN в разных местах — вынесите в VIEW.
Совет: на собесе, если показывают сложный запрос и спрашивают «как улучшить» — предложите VIEW. Это чистота + переиспользование.
Читайте также
FAQ
VIEW медленная — что делать?
Варианты: (1) добавить индексы на базовые таблицы, (2) переписать в materialized view с REFRESH, (3) разбить на несколько view.
MATERIALIZED VIEW vs таблица со снапшотом?
Materialized view автоматически пересчитывается при REFRESH. Таблица — вы сами управляете. Для ежечасных метрик — materialized. Для редких отчётов — таблица через CREATE TABLE AS SELECT.
Можно ли INSERT в VIEW?
Можно в updatable view (простые VIEW без JOIN/GROUP BY). Изменения попадают в базовую таблицу. На практике редко используют.
Разница VIEW и stored procedure?
VIEW — сохранённый SELECT, возвращает «таблицу». Procedure — блок кода, может содержать логику, циклы, DML. В аналитике чаще VIEW.