МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Тема 10. Триггеры как механизм поддержки семантической целостности в БД Цель: изучить принципы создания в активных серверах баз данных. и использования механизма триггеров Задачи: изучить стандартный синтаксис операторов создания триггеров; изучить механизмы учета изменений, которые вызвали срабатывание триггеров с использованием системных таблиц inserted, deleted; изучить принципы отката операций изменения при работе триггеров. Оглавление 10.1. Триггеры ............................................................................................................... 1 10.2. Реализация механизма триггеров для MS SQL server 2000 .................................. 4 Вопросы для самопроверки .......................................................................................... 9 10.1. Триггеры Наличие механизма хранимых процедур серьезно расширило возможности реализации сложных алгоритмов обработки непосредственно на сервере. Однако такая технология требовала постоянного вмешательства клиентов в обработку информации, только клиент мог вызвать хранимую процедуру и запустить соответствующий процесс обработки. В этом случае сервер баз данных оставался пассивным, он не мог анализировать информацию в БД и самостоятельно запускать требуемые процедуры ее обработки. Предложенный механизм триггеров принципиально изменил ситуацию. Теперь сервер получил возможность контролировать состояние БД и самостоятельно запускать требуемые хранимые процедуры. В этом случае сервер становится активным, потому что он сам может запускать хранимые процедуры без участия клиентов. Фактически триггер — это специальный вид хранимой процедуры, которую SQL Server вызывает при выполнении операций модификации соответствующих таблиц. Триггер автоматически активизируется при выполнении операции, с которой он связан. Триггеры связываются с одной или несколькими операциями модификации данных в одной таблице. В разных коммерческих СУБД рассматриваются разные типы триггеров. Так, в ранних версиях MS SQL Server триггеры были определены только как постфильтры, т. е. такие триггеры, которые выполняются после свершения события. В MS SQL Server 2000 имеются триггеры, которые выполняются вместо операции модификации и являются как бы предваряющими триггерами, и старые, типа стандартных триггеров. В СУБД Oracle определены два типа триггеров: триггеры, которые могут быть запущены перед реализацией операции модификации, они называются BEFOREтриггерами, и триггеры, которые активизируются после выполнения соответствующей модификации, аналогично триггерам MS SQL Server, они называются AFTERтриггерами. Но в СУБД Oracle определены еще 2 модификации триггеров: те, которые выполняются для всей таблицы, и те, которые выполняются для каждой строки. В MS SQL Server таких модификаций триггеров не рассматривается. Как наиболее 1 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE молодой элемент триггеры менее всего стандартизированы, и имеется максимальное различие в трактовке и реализации механизмов триггеров в разных СУБД. Триггеры могут быть эффективно использованы для поддержки семантической целостности БД, однако приоритет их ниже, чем приоритет правил-ограничений (constraints), задаваемых на уровне описания таблиц и на уровне связей между таблицами. При написании триггеров всегда надо помнить об этом, при нарушении правил целостности по связям (DRI declarative Referential Integrity) триггер просто может никогда не сработать. В стандарте SQL1 ни хранимые процедуры, ни триггеры были не определены. Но в добавлении к стандарту SQL2, выпущенном в 1996 г., те и другие объекты были стандартизированы и определены. В MS SQL SERVER 2000 появился новый вид триггера — INSTEAD OF триггер. Его принципиальное отличие от обычных (AFTER) триггеров состоит в том, что он выполняется не после выполнения операции вставки/изменения/удаления, а вместо нее. Простейший оператор создания триггера имеет вид: CREATE TRIGGER < имя _ триггера > ON < имя _ таблицы > FOR [DELETE] [,] [INSERT] [,] [UPDATE] AS SQL- операторы (тело триггера) В конкретных СУБД этот оператор может существенным образом отличаться. Для создания триггеров в MS SQL Server 2000 используется специальная команда: CREATE TRIGGER имя _ триггера ON таблица [WITH ENCRYPTION] { {FOR | AFTER | INSTEAD OF }{[DELETE] [,] [INSERT] [,] [UPDATE] } [WITH APPEND] [NOT FOR REPLICATION] AS { IF UPDATE (столбец _i) [ {AND | OR} UPDATE (столбец _j)] [ ... n] | IF (COLUMNS_UPDATED() { побитовый _ оператор } битовая _ маска) {оператор_сравнения} битовая_маска_столбца [... n ] } инструкции_SQL [ ... n ] } } Имя триггера является идентификатором во встроенном языке программирования СУБД и должно удовлетворять соответствующим требованиям. 2 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE В параметре FOR задается тип триггера AFTER (после выполнения операции модификации) и INSTEAD OF (вместо выполнения операции модификации) и одна или несколько операций модификации, которые запускают данный триггер. Опция WITH APPEND необходима, только совместимости не превышает 65 и используется триггеров. если установленный уровень для создания дополнительных Параметр WITH ENCRIPTING имеет тот же смысл, что и для хранимых процедур, он скрывает исходный текст тела триггера. NOT FOR REPLICATION показывает, что триггер не активизируется при модификации таблицы в процессе репликации. IF UPDATE (столбец) — для операций добавления и обновления данных можно определить дополнительные условия на конкретный столбец таблицы; при указании нескольких столбцов они разделяются логическими операторами. IF (COLUMNS_UDATED()) — выше было показано, как можно с помощью конструкции If update (столбец) определять, какие столбцы затрагиваются изменениями. Если необходимо проверить изменяется, ли один конкретный столбец, то она очень удобна, однако если надо построить сложное условие, включающее много столбцов, то хотя с ее помощью и можно достигнуть требуемого оператора, но полученная конструкция будет слишком громоздкой. Для таких случаев предназначена конструкция IF (COLUMNS_UPDATED()...). Результатом функции COLUMNS_UPDATED() является набор битов, каждый из которых отвечает за один столбец таблицы (младший бит соответствует первому столбцу; старший — последнему). Если в операции, вызвавшей срабатывание триггера, была попытка изменить некоторый столбец, то соответствующий бит будет установлен в 1. Побитовый_оператор определяет операцию выделения нужных битов, полученных с помощью COLUMNS_UPDATED(). Обычно используется оператор &. Битовая маска в сочетании с побитовым оператором позволяет выделить интересующие разработчика биты, т. е. определить, изменялись ли в операции, вызвавшей срабатывание триггера, интересующие его столбцы. Оператор_сравнения и битовая_маска_столбца — функция COLUMNS_UPDATED() дает набор битов, соответствующий изменяемым столбцам. С помощью битовой маски и побитового оператора над этим набором битов производится преобразование и получается некий промежуточный результат. С помощью оператора сравнения теперь этот промежуточный результат сравнивается с битовой маской столбца. Если результат сравнения — истина, то тело триггера, т. е. набор инструкций SQL, будет выполнен, иначе — нет. Например, пусть таблица имеет следующую структуру: create table mytable (a int, b int, c int, d int, e int). Пять столбцов соответствуют 5 битам, из которых младший соответствует столбцу a, старший — столбцу e. Пусть операция, приведшая к срабатыванию триггера, изменяет столбцы a, b и e. Тогда функция Columns_updated даст значение 10011. Пусть нас не интересует изменение столбцов b и d, но интересует изменение всех остальных столбцов (a, c и e, т. е. маска будет 10101) (напомним, что на момент написания триггера мы не знаем, какие столбцы затронет на самом деле та или иная операция изменения или вставки, т. е. какой результат даст функция columns_updated()). Задав побитовый оператор сравнения во время выполнения, получим 10011 & 10101, что даст в результате 10001, что в десятичном представлении составляет 17. Сравнив это значение с помощью оператора сравнения и битовой маски столбца, получим ответ — удовлетворяет ли операция изменения/вставки требуемым условиям. Так, например, если бизнес-логика требует, чтобы триггер сработал при изменении все интересующих нас столбцов (a, c, e), 3 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE то, естественно, битовая маска и битовая маска столбца должны иметь одинаковые значения, а оператор сравнения должен быть =. В этом случае для нашего примера, вся конструкция будет иметь вид if (columns_updated & 17)=17. Если же требуется, чтобы изменился хотя бы один из интересующих нас столбцов, то она, очевидно, будет иметь вид if (columns_updated & 17)>0. С помощью битовых операций можно достичь большой гибкости при составлении таких конструкций. Существует несколько правил, которые ограничивают набор операторов, которые могут быть использованы в теле триггера. Так, в большинстве СУБД действуют следующие ограничения. Нельзя использовать в теле триггера операции создания объектов БД (новой БД, новой таблицы, нового индекса, новой хранимой процедуры, нового триггера, новых индексов, новых представлений). Нельзя использовать в триггере команду удаления объектов DROP для всех типов базовых объектов БД. Нельзя использовать в теле триггера команды изменения базовых объектов ALTER TABLE, ALTER DATABASE. Нельзя изменять права доступа к объектам БД, т. е. выполнять команду GRAND или REVOKE. Нельзя создать триггер стандартного типа AFTER для представления (VIEW). В отличие от хранимых процедур, триггер не может возвращать никаких значений, он запускается автоматически сервером и не может связаться самостоятельно ни с одним клиентом. 10.2. Реализация механизма триггеров для MS SQL server 2000 Для отслеживания вносимых изменений в MS SQL Server используются специальные системные таблицы inserted и deleted. Таблица inserted содержит добавленные строки, а таблица deleted — удаленные. Нетрудно догадаться, что при выполнении операции изменения, будет использована и таблица inserted, и таблица deleted. В этом случае старые значения окажутся в таблице deleted, а новые — в таблице inserted. Объединяя их по ключевому столбцу (столбцам), нетрудно определить, какие значения на какие изменены. Структура системных таблиц полностью соответствует модифицируемой таблице, и их можно анализировать в теле триггера: Например, оператором: для поиска всех удаленных значений можно воспользоваться SELECT * FROM deleted Нельзя выполнить триггер, анализируя в столбцах таблиц INSERTED и DELETED значение большого двоичного объекта(BLOB), имеющие тип данных text или image, независимо от того, записывается эта процедура в журнал или нет. Не следует применять инструкции SELECT, возвращающие результирующие наборы из триггера, для приложения-клиента, требующего специального управления результирующими наборами, независимо от того, делается это в хранимой процедуре или нет. Нельзя создавать INSTEAD OF UPDATE и DELETE триггеры на таблицы, имеющие внешние ключи с установленными опциями каскадного изменения или удаления соответственно. 4 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Рассмотрим пример триггера, который срабатывает при удалении экземпляра некоторой книги, например в случае утери этой книги читателем. Что же может делать этот триггер? Он может проверять, остался ли еще хоть один экземпляр данной книги в библиотеке, а если это был последний экземпляр книги в библиотеке, то резонно удалить описание книги из предметного каталога, чтобы читатели зря не пытались заказать эту книгу. Текст этого триггера на языке Transact SQL приведен ниже: CREATE TRIGGER DEL_EXEMP ON dbo.EXEMPLAR /* мы создаем триггер для таблицы EXEMPLAR */ FOR DELETE /* только для операции удаления */ AS /* опишем локальные переменные */ DECLARE @Ntek int /* количество оставшихся экземпляров удаленной книги */ DECLARE @DEL_EX VARCHAR(12) /* шифр удаленного экземпляра*/ Begin /* по временной системной таблице, содержащей удаленные записи, определяем шифр книги, соответствующей последнему удаленному экземпляру */ SELECT @DEL_EX = ISBN From deleted /* вызовем хранимую процедуру, которая определит количество экземпляров книги с заданным шифром */ EXEC @Ntek = COUNT_EX @DEL_EX /* Если больше нет экземпляров данной книги, то удалим запись о книге из таблицы BOOKS */ IF @Ntek = 0 DELETE from BOOKS WHERE BOOKS.ISBN = @DEL_EX END Создавать триггеры можно и с помощью SQL Server Enterprise Manager. Для этого запустите SQL Server Enterprise Manager. В списке объектов базы данных триггеров нет, потому что они не являются самостоятельными независимыми объектами. Триггеры привязаны к конкретной таблице, поэтому надо выбрать таблицу, потом щелкнуть правой кнопкой мыши, и в контекстном меню выбрать команду «Все задачи -> Управление триггерами». В результате этих действий появится диалоговое окно, в котором можно ввести текст триггера и присвоить ему имя (рис. 10.1). После окончания ввода можно выполнить проверку синтаксиса и нажать кнопку ОК для сохранения триггера в базе данных. 5 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Рис. 10.1. Окно редактора триггеров в MS SQL Server 2000 Рассмотрим пример нового INSTEAD OF триггера. INSTEAD OF триггеры отличаются от обычных (AFTER) триггеров тем, что выполняются не после выполнения операции, приведшей к его срабатыванию, а вместо нее, cо всеми вытекающими последствиями (такими, например, как возможность их использования совместно с ограничениями целостности). Системные таблицы Inserted и Deleted используются в них так же, как и в AFTER триггерах. Тело триггера может дублировать операцию, которая вызвала его срабатывание, а может и не дублировать. Другими словами, если мы описываем INSTEAD OF DELETE триггер, то ничто не мешает нам в нем выполнить операцию DELETE, удаляющую все строки, которые и должны были быть удалены в соответствии с вызвавшей триггер операцией, хотя можно этого и не делать. Приведем пример использования INSTEAD OF триггера. Действительно, при наличие ограничений целостности, которые выражены в декларативной ссылочной целостности (DRI), наш предыдущий триггер может сработать только в том случае, если на данную книгу нет ссылок в подчиненных таблицах, которыми являются связующие таблицы BOOKS_CAT, которая связывает книгу с системным каталогом и REALATION_93, которая связывает книгу с ее авторами. Давайте создадим триггер, который будет срабатывать всегда. Однако для этого изменим бизнес-логику (алгоритм) работы предыдущего триггера следующим образом. Перед удалением книги должны быть удалены все ссылки на нее в связующих таблицах, и это действительно корректно, т. к. ссылаться на книгу, ни одного экземпляра которой нет в библиотеке, бессмысленно. Триггер мы прикрепим к таблице BOOKS, он должен срабатывать вместо операции удаления строки из этой таблицы. CREATE TRIGGER DELL_BOOKS ON [dbo].[BOOKS] 6 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE -- задаем тип триггера INSTEAD OF DELETE AS -- опишем переменную, в которую поместим шифр удаляемой книги declare @ISBN varchar(14) -- используя системную таблицу Deleted, определим значение шифра удаляемой книги и присвоим его нашей внутренней переменной @ISBN Select @ISBN=ISBN from Deleted -- удалим сначала из подчиненных таблиц все записи с заданным значением шифра книги Delete from BOOKS_CAT where BOOKS_CAT.ISBN=@ISBN Delete from Relation_93 where RELATION_93.ISBN= @ISBN -- теперь удалим книгу Delete from books where Books.isbn= @ISBN Можно также создать триггеры, выполняющие работу только в случае обновления конкретного столбца. Для принятия решения о продолжении обработки в триггере может быть применена инструкция IF UPDATE: IF UPDATE(au_lname) AND (@@ROWCOUNT=1) BEGIN ... END Код внутри блока выполняется только в том случае, если обновляется столбец au_ I name. Всегда помните, что обновляемый столбец изменяется не во всех случаях. Если после анализа данных, который проводится в теле триггера, выясняется, что действие, на которое «повешен» (запрограммирован) триггер, необходимо отменить, то выполняется операция отката ROLLBACK. Рассмотрим триггер, который бы не позволял удалить экземпляр книги, если этот экземпляр в данный момент находится на руках у читателя. Для отмены команды удаления применить команду отката транзакций ROLLBACK. CREATE TRIGGER del_ex2 ON [dbo].[EXEMPLAR] FOR DELETE AS /* триггер на удаление экземпляров проверяет наличие читателя данного экземпляра книги существует, то запрещаем удаление в библиотеке и, если такой определим переменную для хранения значения параметра присутствие в библиотеке */ DECLARE @Yes_no VARCHAR(1) 7 читатель МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE /* получим из системной таблицы deleted в библиотеке удаленного экземпляра */ значение параметра присутствие SELECT @Yes_no = YES_NO From deleted -- проверяем значение данного параметра -- откатываем транзакцию или ничего не делаем, т.е. разрешаем удаление экземпляра. IF @Yes_no =’0’ rollback В общем случае не существует ограничения на количество триггеров. Стандартных триггеров типа AFTER для каждой операции модификации может быть разработано несколько. Все триггеры анализируются последовательно в порядке их создания. В отличие от After-триггеров существует ограничение на триггеры Instead Of. На каждую операцию модификации может быть создан только один триггер Триггеры можно встраивать друг в друга, допускается 32 уровней вложенности. Если операции вложенного триггера нежелательны, SQL Server можно сконфигурировать так, чтобы отключить их. Примечание: уровень вложенности триггера можно проверить в любое время, опросив значение, установленное в переменной @@NESTLEVEL. Оно должно находиться в пределах от 0 до 32. Вложенные триггеры могут привести к рекурсии. Рекурсия бывает двух видов: прямая и косвенная. Прямая рекурсия получается в случае, если срабатывание триггера приводит к изменениям, которые вновь вызывают его же. Косвенная рекурсия получается, когда срабатывание триггера приводит к изменениям, которые приводят к срабатыванию другого триггера, что в свою очередь приводит к изменениям, вызывающим срабатывание первого триггера. Конечно же, цепочка может состоять не только из двух, но и из большего числа триггеров. Прямую рекурсию можно отключить (и включить) с помощью опции БД recursive triggers. Отключить (и включить) косвенную рекурсию, равно как и вложенность триггеров вообще, можно с помощью серверной опции nested_triggers. Эта опция определяет возможность вложенности триггеров не для одной конкретной БД, а для всего сервера. Следует отметить, что INSTEAD OF триггеры по своей природе не подвержены прямой рекурсии. При создании триггера SQL Server не может самостоятельно распознать, что некоторая вложенная конструкция вызывает бесконечный цикл. Подобный факт может быть установлен только во время выполнения этого триггера. Предположим, что таблица Table_A включает триггер trigger_A, который выполняется, когда происходит обновление Таblе_А. При выполнении trigger_a вызывает обновление таблицы таblе B. Эта таблица включает в себя триггер trigger_b, который выполняется, когда обновляется таblе_в, и вызывает обновление таблицы таblе_A. Таким образом, если пользователь обновляет любую из этих двух таблиц, два триггера продолжают бесконечно вызывать выполнение друг друга. При возникновении такой ситуации SQL Server закрывает или отменяет выполнение триггера. Для отслеживания количества измененных строк служит глобальная переменная @@ROWCOUNT. Она доступна любому триггеру и позволяет контролировать количество изменяемых строк таблицы. Кроме того, для передачи сообщений об ошибках пользовательскому процессу служит функция RAISERROR. Ее формат RAISERROR(<выражение_integer>|<’текст сообщения об ошибке’>, 8 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE [уровень серьезности ошибки] [, номер состояния ошибки]) Выражение_integer — указанное пользователем сообщение об ошибке или номер ошибки. Его значение должно лежать в пределах от 50 000 до 2 147 483 647. Это значение помещается в глобальную переменную @@ERROR, которая сохраняет последний возвращенный номер ошибки. Сообщение об ошибке должно быть определено в виде строковой переменной. Вопросы для самопроверки 1. Какой командой можно запустить триггер? 2. Какой командой можно создать триггер? 3. Могут ли быть параметры триггера и как они ему передаются? 4. Что такое DRI и какое отношение эта аббревиатура имеет к триггерам? 5. Чем отличаются следующие триггеры: Create trigger D_1 On R1 FOR DELETE AS Declare @n int, @k int Select @k =K from Deleted Select @n=count(*) from R1 Where K=@k If @n >1 rollback Create trigger D_2 On R1 AFTER DELETE AS Declare @n int, @k int Select @k =K from Deleted Select @n=count(*) from R1 Where K=@k If @n >1 rollback 6. Необходимо удалить автора из БД «Библиотека». Какой триггер для этого надо разработать? 9