Как создать 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+ вопросами для собесов.