VIEW в SQL — что это и зачем нужны представления

Что такое VIEW

VIEW (представление) — это именованный SQL-запрос, сохранённый в базе данных. При обращении к VIEW база выполняет этот запрос и возвращает результат как обычную таблицу. Самих данных VIEW не хранит — это виртуальная таблица.

Проще говоря: вы пишете сложный SELECT один раз, даёте ему имя и дальше используете это имя вместо того, чтобы копировать запрос по всему коду.

CREATE VIEW — синтаксис

CREATE VIEW active_users AS
SELECT
    user_id,
    COUNT(*) AS sessions,
    MAX(created_at) AS last_seen
FROM user_sessions
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id;

После создания active_users используется как таблица:

SELECT * FROM active_users WHERE sessions > 10;

База данных подставляет сохранённый запрос каждый раз, когда вы обращаетесь к VIEW. Никакого кеширования, никакого предвычисления — просто алиас для SELECT.

Зачем нужны представления

1. Упрощение сложных запросов

Аналитик каждый день считает метрики из 4 таблиц с 3 JOIN и 2 подзапросами. Без VIEW этот запрос копируется в 10 дашбордов. С VIEW — вся логика инкапсулирована:

CREATE VIEW daily_metrics AS
SELECT
    DATE_TRUNC('day', o.created_at) AS day,
    COUNT(DISTINCT o.user_id) AS buyers,
    COUNT(*) AS orders,
    SUM(o.amount) AS revenue,
    SUM(o.amount) / COUNT(DISTINCT o.user_id) AS arpu
FROM orders o
WHERE o.status = 'completed'
GROUP BY 1;

Теперь SELECT * FROM daily_metrics WHERE day >= '2026-04-01' — и всё. Логика расчёта в одном месте, а не размазана по десяти запросам.

2. Ограничение доступа

VIEW позволяет дать пользователю доступ к части данных без доступа к исходной таблице:

CREATE VIEW public_users AS
SELECT user_id, username, city, created_at
FROM users;
-- GRANT SELECT ON public_users TO analyst_role;

Аналитик видит public_users, но не видит email, phone, salary из таблицы users.

3. Единообразная логика

Если определение «активный пользователь» меняется — правите один VIEW, а не 20 запросов в разных скриптах.

Использование VIEW в запросах

VIEW работает везде, где работает таблица — в SELECT, JOIN, подзапросах:

-- JOIN с VIEW
SELECT u.username, m.orders, m.revenue
FROM users u
JOIN (SELECT * FROM daily_metrics) m ON ... -- не нужно, VIEW уже есть

-- Лучше: VIEW с сегментами пользователей
CREATE VIEW user_segments AS
SELECT
    user_id,
    CASE
        WHEN total_spent > 10000 THEN 'vip'
        WHEN total_spent > 1000 THEN 'regular'
        ELSE 'new'
    END AS segment
FROM (
    SELECT user_id, COALESCE(SUM(amount), 0) AS total_spent
    FROM orders
    GROUP BY user_id
) t;

-- Использование
SELECT s.segment, COUNT(*) AS users
FROM user_segments s
GROUP BY s.segment;

Обновление и удаление

-- Заменить определение VIEW
CREATE OR REPLACE VIEW active_users AS
SELECT user_id, COUNT(*) AS sessions
FROM user_sessions
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'  -- было 30, стало 7
GROUP BY user_id;

-- Удалить VIEW
DROP VIEW active_users;

-- Удалить, только если существует
DROP VIEW IF EXISTS active_users;

CREATE OR REPLACE удобен: не нужно дропать и пересоздавать. Но он не позволяет менять список столбцов — только логику запроса.

MATERIALIZED VIEW

Обычный VIEW выполняет запрос при каждом обращении. Если запрос тяжёлый (агрегация миллионов строк), это медленно. Материализованное представление сохраняет результат на диск:

CREATE MATERIALIZED VIEW monthly_revenue AS
SELECT
    DATE_TRUNC('month', created_at) AS month,
    COUNT(*) AS orders,
    SUM(amount) AS revenue
FROM orders
GROUP BY 1;

Данные вычисляются один раз и хранятся физически. Запрос SELECT * FROM monthly_revenue читает готовые данные — моментально.

Но данные устаревают. Чтобы обновить:

REFRESH MATERIALIZED VIEW monthly_revenue;

-- С CONCURRENTLY — без блокировки чтения (нужен UNIQUE INDEX)
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_revenue;

Когда использовать: тяжёлые аналитические агрегаты, которые не нужны в реальном времени. Дашборды, которые обновляются раз в час или раз в день. Отчёты по историческим данным.

VIEW vs CTE vs подзапрос

VIEW CTE (WITH) Подзапрос
Хранится в БД Да Нет — только в рамках запроса Нет
Переиспользование Между запросами Внутри одного запроса Нет
Доступ другим пользователям Да Нет Нет
Материализация На диск (REFRESH) Внутри запроса (PG 12+ MATERIALIZED) Нет
Когда использовать Логика нужна в нескольких запросах/скриптах Несколько шагов внутри одного запроса Простая одноразовая фильтрация

VIEW — для повторяющейся логики между запросами. CTE — для структурирования одного запроса. Подзапрос — для простых случаев. Подробнее о CTE и подзапросах — в отдельном разборе.

Типичные ошибки

1. VIEW поверх VIEW поверх VIEW

CREATE VIEW v1 AS SELECT ... FROM big_table;
CREATE VIEW v2 AS SELECT ... FROM v1 JOIN another_table ...;
CREATE VIEW v3 AS SELECT ... FROM v2 WHERE ...;

Три уровня вложенности VIEW — и вы получаете запрос, который никто не может отладить. Оптимизатор разворачивает все VIEW в один запрос, и план выполнения становится непредсказуемым. Правило: один уровень вложенности VIEW нормально, два — максимум.

2. Забыть обновить MATERIALIZED VIEW

Создали материализованное представление, используете его в дашборде — а данные за вчера не появляются. Потому что REFRESH не запущен. Решение: настроить cron-задачу или pg_cron для автоматического обновления.

3. Обновляемый VIEW без понимания ограничений

В PostgreSQL можно делать INSERT/UPDATE/DELETE через простые VIEW (без JOIN, GROUP BY, DISTINCT). Но если VIEW содержит агрегацию или JOIN — запись невозможна. На собеседовании это частый вопрос.

Вопросы с собеседований

  • «Что такое VIEW и чем отличается от таблицы?» — VIEW не хранит данные. Это сохранённый запрос, который выполняется при каждом обращении. Таблица хранит данные физически на диске.

  • «Зачем нужен MATERIALIZED VIEW?» — Для кеширования результатов тяжёлых запросов. Данные вычисляются один раз и хранятся на диске. Обновляются по команде REFRESH. Подходит для дашбордов и аналитических отчётов.

  • «Можно ли делать INSERT в VIEW?» — Да, если VIEW простой: один SELECT из одной таблицы, без GROUP BY, DISTINCT, UNION, оконных функций. В остальных случаях — нет (или нужен INSTEAD OF триггер).

  • «VIEW замедляет запрос?» — Обычный VIEW — нет, это просто макрос. База разворачивает VIEW и оптимизирует итоговый запрос как единое целое. Проблемы возникают при глубокой вложенности VIEW в VIEW.

  • «Чем MATERIALIZED VIEW отличается от обычного VIEW?» — Обычный VIEW выполняет запрос каждый раз. Materialized VIEW хранит предвычисленный результат на диске. Быстрее при чтении, но данные могут устареть — нужен REFRESH.

FAQ

VIEW — это копия таблицы?

Нет. Обычный VIEW не хранит данных. Это сохранённый запрос, который выполняется при каждом обращении. Если данные в исходных таблицах изменились — VIEW автоматически покажет новые данные. Копия данных — это MATERIALIZED VIEW.

Как часто обновлять MATERIALIZED VIEW?

Зависит от задачи. Дашборд с дневными метриками — раз в сутки. Агрегат для поиска — раз в час. Критичные данные в реальном времени — не используйте MATERIALIZED VIEW, используйте обычный VIEW или прямой запрос. Типичный вариант: REFRESH MATERIALIZED VIEW CONCURRENTLY по cron-расписанию.

VIEW есть во всех СУБД?

Обычные VIEW поддерживают все основные СУБД: PostgreSQL, MySQL, ClickHouse, SQL Server, Oracle. MATERIALIZED VIEW есть в PostgreSQL и Oracle. В MySQL и SQL Server материализованных представлений нет нативно (в SQL Server есть indexed views — похожая концепция). В ClickHouse для этого используются движки таблиц (MaterializedView — это триггер, а не кеш).

VIEW тормозит запрос?

Сам по себе — нет. Обычный VIEW — это алиас для SELECT. Оптимизатор разворачивает его и строит план как для обычного запроса. Проблемы начинаются, если: (1) VIEW вложены друг в друга на 3+ уровня; (2) VIEW содержит тяжёлую логику, а вы используете его как таблицу в JOIN с другим тяжёлым VIEW. Решение — выносить тяжёлые части в MATERIALIZED VIEW.


Хотите потренировать VIEW и другие SQL-темы на реальных задачах с собеседований? Попробуйте SQL-тренажёр или откройте тренажёр прямо в Telegram. А если хотите увидеть, какие вопросы задают на собесах — загляните в примеры вопросов.