Представление — это сохранённый запрос
Представление в 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 выводит представления вместе с таблицами.