Как создать VIEW в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем аналитику знать VIEW
VIEW — один из самых недооценённых инструментов в SQL для аналитика. С их помощью сложный запрос из 50 строк становится «таблицей», которую можно переиспользовать в дашборде, отчёте или другом запросе. Бизнес видит SELECT * FROM kpi_dashboard, не зная, что за этим — пять JOIN и три агрегации.
VIEW критичны в ETL и data modeling: вы можете скрыть PII за view с урезанными колонками и дать аналитикам доступ только к безопасной части. Materialized view (физически хранит результат) ускоряют дашборды на больших данных в разы. Если работаете в DWH — это must-have.
В статье — практическое руководство:
- Создание обычного VIEW с JOIN и агрегацией
- Materialized view и его REFRESH
- VIEW vs materialized view (когда что выбрать)
- Индексы на materialized view (ускорение в 10×)
- VIEW для ограничения доступа (security)
- Обновление и удаление VIEW
- Ограничения: nested views, updates через view
Что такое VIEW
VIEW — сохранённый SQL-запрос, который выглядит как таблица. При обращении выполняется базовый SELECT.
Базовый синтаксис
CREATE VIEW active_users AS
SELECT *
FROM users
WHERE is_active = TRUE;Теперь можно использовать как таблицу:
SELECT COUNT(*) FROM active_users;1. VIEW с агрегацией
CREATE VIEW daily_revenue AS
SELECT
DATE(created_at) AS day,
SUM(total) AS revenue,
COUNT(*) AS orders
FROM orders
WHERE status = 'paid'
GROUP BY 1;2. VIEW с JOIN
CREATE VIEW users_with_orders AS
SELECT
u.id,
u.name,
u.email,
COUNT(o.order_id) AS total_orders,
SUM(o.total) AS total_spent
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name, u.email;3. Обновить VIEW
Способ 1: CREATE OR REPLACE
CREATE OR REPLACE VIEW active_users AS
SELECT * FROM users
WHERE is_active = TRUE AND last_login > NOW() - INTERVAL '30 days';Способ 2: DROP + CREATE
DROP VIEW active_users;
CREATE VIEW active_users AS ...;4. Удалить VIEW
DROP VIEW active_users;
-- безопасно, если VIEW может не существовать
DROP VIEW IF EXISTS active_users;5. Materialized view
MATERIALIZED VIEW физически сохраняет результат. Быстрее при чтении, но устаревает.
CREATE MATERIALIZED VIEW monthly_revenue AS
SELECT
DATE_TRUNC('month', created_at) AS month,
SUM(total) AS revenue
FROM orders
GROUP BY 1;Обновление materialized view
-- Postgres
REFRESH MATERIALIZED VIEW monthly_revenue;
-- без блокировки (параллельно)
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_revenue;6. VIEW vs Materialized view
| VIEW | Materialized VIEW | |
|---|---|---|
| Хранение | только запрос | результат физически |
| Скорость чтения | как базовый запрос | быстрее |
| Актуальность | всегда свежее | устаревает до REFRESH |
| Обновление | мгновенно (всегда видит live) | надо refresh |
| Поддержка в MySQL | да (view) | нет (только через табл + триггер) |
7. Индексы на materialized view
Materialized view можно индексировать — это существенно ускоряет запросы:
CREATE INDEX idx_mv_month ON monthly_revenue(month);На обычный VIEW — нельзя.
8. Permissions / security
VIEW часто используют для ограничения доступа:
-- общая таблица orders с sensitive данными
-- создаём view без них
CREATE VIEW public_orders AS
SELECT order_id, total, created_at -- без customer_email, phone
FROM orders;
GRANT SELECT ON public_orders TO analyst_role;9. Где применяется
Analytics
-- упрощение сложного запроса
CREATE VIEW kpi_dashboard AS
SELECT
DATE_TRUNC('month', day) AS month,
SUM(revenue) AS mrr,
SUM(new_customers) AS new_customers
FROM daily_stats
GROUP BY 1;ETL
-- нормализованное представление сырой таблицы
CREATE VIEW orders_clean AS
SELECT
order_id,
LOWER(TRIM(customer_email)) AS email,
COALESCE(total, 0) AS total,
CAST(created_at AS DATE) AS order_date
FROM raw_orders
WHERE status != 'test';Legacy migration
-- старое приложение ждёт таблицу с старой структурой
CREATE VIEW old_users AS
SELECT id, name AS user_name, email AS user_email FROM users;10. Ограничения
Не все VIEW можно обновлять
-- обычно можно
UPDATE active_users SET name = 'x' WHERE id = 1;
-- внутренне обновляет users
-- нельзя обновлять VIEW с GROUP BY / DISTINCT / агрегациямиNested VIEWs
VIEW может ссылаться на другую VIEW. Но производительность страдает.
Materialized view нельзя обновлять частично
В Postgres REFRESH перезаписывает весь результат. Нельзя «добавить новые данные».
Частые ошибки
1. VIEW = performance улучшение
Нет. VIEW — это синтаксический сахар. Запрос выполняется каждый раз.
Materialized view — да, ускоряет.
2. Забывать REFRESH
Materialized view без refresh — видите устаревшие данные.
3. Nested VIEW на 5 уровней
SELECT из view, который из view, который из view... — медленно и нечитаемо.
4. Permission-based VIEW без security_barrier
Пользователь может использовать подзапрос, чтобы обойти фильтр VIEW. Для защиты:
CREATE VIEW secure_users WITH (security_barrier) AS
SELECT ... WHERE tenant_id = current_tenant();Связанные темы
FAQ
VIEW замедляет?
Нет. Запрос оптимизируется планировщиком как обычный SELECT.
Materialized view в MySQL?
Нет нативно. Эмулируют через таблицу + cron-job / триггер для обновления.
Когда materialized view?
Когда запрос дорогой, а данные редко меняются. Дашборды, отчёты.
Удалить view — данные удалятся?
Нет. VIEW — только запрос. Базовые таблицы не трогаются.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.