Menu

Представления в SQLite: CREATE VIEW и INSTEAD OF

Разбираем представления в SQLite: как сохранить запрос как виртуальную таблицу, зачем нужны временные VIEW и почему по умолчанию они доступны только для чтения.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Представление — это сохранённый запрос

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

paid_orders снаружи выглядит и ведёт себя как обычная таблица. Есть колонки, можно делать SELECT, можно джойнить. Но внутри каждый запрос всё равно разворачивается в исходный фильтр WHERE status = 'paid'.

В этом и вся суть: представление в SQLite — это просто псевдоним для запроса.

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

Главный плюс — именование. Громоздкий запрос получает короткое и понятное имя, а остальной код остаётся читаемым:

Без представления каждый, кто пишет запрос, тащил бы GROUP BY сам — и любой мог бы напутать с фильтром. А так агрегация описана один раз. Вызывающему коду достаточно обратиться к customer_totals и при необходимости навесить сверху свои фильтры.

Ещё представления удобно использовать как способ разграничить доступ к данным. Если в запросе не должно светиться поле password_hash, заведите view, в который попадают все колонки кроме этой, и пусть приложение работает уже с ним.

Синтаксис CREATE VIEW

Полная форма выглядит так:

CREATE [TEMPORARY] VIEW [IF NOT EXISTS] view_name [(column_aliases)] AS
SELECT ...;

Что полезно держать в голове:

  • IF NOT EXISTS молча пропустит создание, если такое представление уже есть в базе.
  • TEMPORARY (или сокращённо TEMP) делает временное представление — оно исчезнет, как только закроется соединение.
  • Псевдонимы колонок в скобках позволяют переименовать столбцы представления, не трогая сам SELECT.

Само представление выдаёт более понятные имена (item, dollars), при этом колонки в исходной таблице переименовывать не приходится.

Замена и удаление представлений

В SQLite нет ни CREATE OR REPLACE VIEW, ни ALTER VIEW. Чтобы изменить определение представления, его нужно удалить и создать заново:

DROP VIEW IF EXISTS active_orders; — это безопасный вариант: если представления нет, ошибки не будет. Удаление view никак не затрагивает исходные таблицы — вы стираете только сохранённый запрос.

Временные представления в SQLite

TEMP VIEW живёт ровно столько, сколько открыто текущее подключение к базе. Закрыли соединение — представления больше нет. Это удобно для разовых сессий аналитики, когда не хочется оставлять после себя лишние определения:

Временные представления удобны ещё и тем, что позволяют «перекрыть» имя запроса, не закрепляя его в схеме — то, что нужно во время экспериментов.

По умолчанию представления доступны только для чтения

Это самый коварный момент. Напрямую через представление нельзя выполнить ни INSERT, ни UPDATE, ни DELETE:

sqlite> INSERT INTO paid_orders (customer, amount) VALUES ('Eve', 50);
Ошибка времени выполнения: невозможно изменить paid_orders, потому что это представление

Решение — триггеры INSTEAD OF. Вы пишете триггер, который срабатывает вместо попытки записи и превращает её в настоящую операцию над базовой таблицей:

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

Материализованные представления в SQLite — делаем сами

В некоторых СУБД можно закэшировать результат представления на диске и обновлять его по запросу. В SQLite такого нет. Каждое чтение из view заново выполняет исходный запрос. Для большинства задач это нормально — SQLite работает быстро, а планировщик запросов вполне толковый. Но если у вас тяжёлая агрегация, к которой обращаются часто, заведите обычную таблицу и поддерживайте её в актуальном состоянии вручную:

Дальше такой кэш приходится обновлять либо по расписанию, либо вешать триггеры на orders, чтобы он не устаревал. Возни хватает — но в SQLite других вариантов попросту нет.

Список представлений в sqlite_master

Метаданные представлений хранятся в sqlite_master бок о бок с таблицами и индексами:

Колонка sql возвращает исходный текст CREATE VIEW — пригодится, когда забыл, что делает представление. В CLI то же самое выводит .schema view_name, только аккуратнее.

Когда стоит использовать представление

Представления оправдывают себя в таких случаях:

  • Нетривиальный запрос повторяется в трёх и более местах. Один раз дать ему имя лучше, чем копипастить.
  • Нужно показать части приложения только определённый срез колонок или строк.
  • Агрегация по смыслу — это единая сущность (monthly_sales, active_users), и вызывающий код должен обращаться к ней как к существительному.

А вот когда представление не нужно:

  • Запрос используется ровно в одном месте. Просто впишите его на месте.
  • Важна производительность, а сам запрос тяжёлый — вы платите эту цену при каждом чтении. Лучше сложить результат в обычную таблицу.
  • Представление ссылается на другое представление, которое ссылается на третье. SQLite со вложенностью справляется, но цепочка из трёх-четырёх view превращает реальный SQL в кашу при отладке.

Дальше: триггеры

Представления и триггеры часто идут в связке — паттерн INSTEAD OF, который позволяет писать через view в SQLite, и есть одна из главных причин существования триггеров. Но и сами по себе триггеры полезны: для аудита, каскадных обновлений и поддержания инвариантов. Об этом — на следующей странице.

Часто задаваемые вопросы

Что такое представление (VIEW) в SQLite?

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

Можно ли делать INSERT или UPDATE через представление в SQLite?

Напрямую — нет. Представления в SQLite по умолчанию доступны только для чтения, поэтому INSERT, UPDATE и DELETE к ним завершатся ошибкой. Сделать VIEW записываемым можно с помощью триггеров INSTEAD OF — они перехватывают запись и транслируют её в операции над реальными таблицами.

Поддерживает ли SQLite материализованные представления?

Нет. В SQLite есть только обычные (виртуальные) представления — запрос пересчитывается при каждом обращении. Если нужен закешированный результат, заведите обычную таблицу и обновляйте её сами либо повесьте триггер, который будет синхронизировать её с исходными таблицами.

Как посмотреть список всех представлений в базе SQLite?

Запросом к sqlite_master: SELECT name FROM sqlite_master WHERE type = 'view';. В CLI команда .schema покажет тексты CREATE VIEW, а .tables выводит представления вместе с таблицами.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ