Триггер запускает SQL автоматически
Триггер — это сохранённый блок SQL, который срабатывает каждый раз, когда на конкретной таблице происходит определённое событие. Пишете его один раз, а уже сама SQLite берёт на себя «когда именно» его выполнить.
Общий вид:
Мы ни разу не писали INSERT в price_history руками — всё сделал триггер. И теперь любое будущее изменение цены будет логироваться точно так же, неважно откуда оно пришло: из консоли, скрипта или приложения.
Разбираем CREATE TRIGGER по косточкам
Давайте пройдёмся по синтаксису шаг за шагом:
CREATE TRIGGER trigger_name
{ BEFORE | AFTER | INSTEAD OF } { INSERT | UPDATE [ OF column_list ] | DELETE }
ON table_name
[ FOR EACH ROW ]
[ WHEN condition ]
BEGIN
-- одна или несколько инструкций
END;
- Момент срабатывания —
BEFOREвыполняется до изменения,AFTER— после, аINSTEAD OFподменяет операцию (только для представлений). - Событие — какая операция запускает триггер.
UPDATE OF col1, col2сужает срабатывание до конкретных столбцов. - Таблица — та, за которой ведётся наблюдение.
FOR EACH ROW— SQLite поддерживает только построчные триггеры, так что эта конструкция подразумевается по умолчанию. Её можно писать явно для ясности — на поведение это никак не влияет.WHEN— необязательное условие. Тело триггера выполнится, только если оно истинно.- Тело — один или несколько операторов между
BEGINиEND. Каждый обязательно заканчивается точкой с запятой.
Вот, собственно, и весь синтаксис. Большинство реальных триггеров умещаются в пять–десять строк.
OLD и NEW: строка, которая меняется
Внутри тела триггера доступны две псевдострокы, через которые можно посмотреть данные:
NEW— новая (входящая) строка. Доступна в триггерах наINSERTиUPDATE.OLD— текущая (старая) строка. Доступна в триггерах наUPDATEиDELETE.
В триггере на DELETE есть только OLD. В триггере на INSERT — только NEW. А в триггере на UPDATE доступны обе.
Удалённая строка исчезает из accounts, но перед этим её данные успевают попасть в deletions.
BEFORE: проверка или подгонка строки
Триггеры BEFORE срабатывают до того, как изменение реально запишется на диск. Это удобно, когда нужно бросить ошибку или нормализовать данные:
Второй INSERT прерывается ещё до того, как в таблицу попадёт хоть одна строка. RAISE(ABORT, '...') отменяет текущую инструкцию и откатывает её до начала, а RAISE(FAIL, ...), RAISE(ROLLBACK, ...) и RAISE(IGNORE) позволяют тоньше управлять тем, что произойдёт дальше.
Если речь о простой валидации данных, лучше использовать ограничения CHECK — они декларативны, и оптимизатор о них знает. Триггер BEFORE пригодится тогда, когда правило должно заглядывать в другие таблицы или делать то, что CHECK выразить не способен.
WHEN в триггере sqlite: условные триггеры
Условие WHEN отбирает, какие именно изменения строк запустят тело триггера. Оно вычисляется для каждой строки уже после того, как OLD и NEW получили свои значения:
Первый заказ не проходит фильтр. А два других — попадают в таблицу. Без условия WHEN каждая вставка дублировалась бы в big_orders, и фильтровать пришлось бы уже при чтении.
INSTEAD OF trigger в SQLite: делаем представление записываемым
По умолчанию представления (views) в SQLite доступны только для чтения. Триггер INSTEAD OF перехватывает попытку записи в представление и вместо неё выполняет ваш SQL — как правило, превращая операцию в запись по нижележащим таблицам:
Приложение обращается к представлению как к обычной таблице, а триггер уже сам разбирается, как разложить значение по first_name и last_name.
Список триггеров и их удаление
Триггеры хранятся в sqlite_master рядом с таблицами и индексами:
DROP TRIGGER IF EXISTS name; — это безопасная форма. А если удалить таблицу, к которой привязан триггер, он удалится автоматически — предварительно подчищать ничего не нужно.
Подводные камни, о которых стоит знать
Несколько моментов, на которых обычно спотыкаются новички:
- Триггер срабатывает на каждую строку, а не на запрос целиком. Если
UPDATEзатронул 1000 строк — триггер выполнится 1000 раз. И если тело триггера само по себе тяжёлое, накладные расходы быстро становятся ощутимыми. - Триггер выполняется внутри той же транзакции. Если внешний запрос откатится, изменения, сделанные триггером, тоже откатятся. Обычно это как раз то, что нужно, но имейте в виду: триггер — не способ «залогировать что бы ни случилось».
- Рекурсивные триггеры по умолчанию отключены. Триггер, изменяющий ту же таблицу, не вызовет сам себя повторно, пока вы не выставите
PRAGMA recursive_triggers = ON;. Без явной необходимости лучше не трогать. - Запись из приложения может обойти триггер только если идёт мимо базы. Пока все изменения проходят через SQLite, триггер сработает. Даже ORM, отправляющие сырой SQL пачками, триггеры запускают.
- Не размазывайте бизнес-логику по куче триггеров. С точки зрения вызывающего кода они невидимы — тому, кто потом будет разбираться, «откуда взялась эта строка», придётся гриповать
sqlite_master. Используйте триггеры для сквозных задач (аудит, производные колонки, запись через представления), а остальную логику держите в коде приложения.
Реалистичный пример: журнал аудита
Соберём всё вместе — отслеживаем каждое изменение в таблице posts:
Один триггер одновременно держит updated_at в актуальном состоянии и пишет строку в журнал аудита — и всё это в одном месте. Коду приложения, который делает UPDATE, ни про то, ни про другое знать не нужно.
Что дальше: работа с JSON
Триггеры закрывают автоматизацию вокруг событий со строками. Следующая продвинутая возможность SQLite — это то, что можно хранить внутри самой строки, то есть JSON. В SQLite есть полноценный набор JSON-функций: можно запрашивать и обновлять структурированные данные, не выходя за пределы SQL. Об этом и поговорим на следующей странице.
Часто задаваемые вопросы
Что такое триггер в SQLite?
Триггер — это блок SQL-кода, который автоматически срабатывает на определённое событие в таблице: INSERT, UPDATE или DELETE. Один раз создаёте его через CREATE TRIGGER, и дальше SQLite сам запускает его при каждом таком событии. Это удобно для журналов аудита, поддержания производных колонок или проверки правил, чтобы не полагаться на то, что приложение всё это вызовет руками.
Чем отличаются триггеры BEFORE, AFTER и INSTEAD OF?
BEFORE срабатывает до того, как изменение применится к строке — подходит для валидации или подправки значений. AFTER запускается уже после изменения — пригодится для логирования или синхронизации других таблиц. INSTEAD OF работает только на представлениях (views) и полностью подменяет операцию, давая возможность сделать view доступным для записи.
Как обратиться внутри триггера к строке, которая меняется?
Используйте NEW.column для новой строки в INSERT и UPDATE, и OLD.column для прежней строки в UPDATE и DELETE. В триггере на INSERT доступен только NEW, в DELETE — только OLD, а в UPDATE — оба. Эти ссылки относятся к конкретной строке, которая обрабатывается прямо сейчас.
Как посмотреть список триггеров и удалить ненужный?
Все триггеры лежат в sqlite_master: запрос SELECT name, tbl_name FROM sqlite_master WHERE type = 'trigger'; покажет их полный список. Для удаления используйте DROP TRIGGER trigger_name; или DROP TRIGGER IF EXISTS trigger_name;, если не уверены, существует ли он. При удалении таблицы её триггеры удаляются автоматически.