В SQL Server тригерите са обекти на база данни, които се изпълняват всеки път, когато се случи задействащо събитие в базата данни или сървъра.
Тригерите играят ключова роля при постигането на бизнес изисквания като предупреждаване на целевите хора, започване на работа или други операции. Тъй като тригерите могат да се справят с много такива операции, трябва да ги дефинираме внимателно, за да избегнем въздействието върху производителността.
В тази статия ще разгледаме тригерите, видовете тригери и различните налични опции за задействане. Освен това ще проучим необходимите предпазни мерки, докато използваме DML тригери.
Задействания в SQL
Тригерът е специален тип Съхранена процедура, която се изпълнява при определени събития, изпълнявайки скрипта, дефиниран в тялото на тригера. Има няколко типа задействания:
- DML тригери – за изпълнение на DML операции като команди INSERT, UPDATE и DELETE върху таблици.
- DDL задействания – за изпълнение на DDL операции като команди CREATE, ALTER и DROP във всякакви обекти в базата данни или сървъра.
- Задействания за влизане – за опита за влизане в екземпляр на SQL Server по време на събитието LOGON.
DML тригери в SQL Server
DML тригерите са тези, задействани от DML команди (INSERT, UPDATE или DELETE) на таблици или изгледи. Можем да създадем такива тригери в тези таблици или изгледи само там, където се намират данните, така че да приемат DML команди върху тях.
Въз основа на времето на задействане/извикване, DML тригерите могат да бъдат от следните типове:
- ЗА или СЛЕД Тип тригер – тригерът се извиква след успешното завършване на DML израза в таблица или изглед. Забележка:възможно е да се създаде тригера AFTER само на таблици, а не на изгледи.
- ВМЕСТО Тип тригер – Тригерът ще бъде извикан преди (ВМЕСТО) DML скрипта, изпълнен в таблицата или изгледа.
SQL Server създава две специални или логически таблици с име INSERTED и АКТУАЛИЗИРАНО всеки път, когато DML тригери се създават в таблици или изгледи. Тези логически таблици помагат да се идентифицират промените в записа, които се случват чрез операции INSERT/UPDATE/DELETE. По този начин гарантира, че DML тригерите функционират ефективно.
- ВЪВЕДЕНО логическата таблица съхранява копия на нови записи на записи, променени по време на операциите INSERT и UPDATE. Когато към действителната таблица се добави нов запис, той се добавя и към INSERTED таблицата. По подобен начин всякакви промени в съществуващи записи чрез оператора UPDATE преместват най-новите стойности към INSERTED таблицата и по-старите стойности – към DELETED логическата таблица.
- ИЗтрито логическата таблица съхранява копия на по-стари стойности по време на операциите UPDATE и DELETE. Всеки път, когато даден запис се актуализира, по-старите стойности се копират в таблицата DELETED. Всеки път, когато запис се изтрие от действителната таблица, записите се вмъкват в таблицата ИЗТРИТА.
SQL Server има вградени функции COLUMN_UPDATED() и UPDATE() за идентифициране на наличието на операции INSERT или UPDATE в конкретната колона.
- COLUMN_UPDATED() връща переменни стойности на колони, които са били засегнати от операциите INSERT или UPDATE.
- АКТУАЛИЗИРАНЕ() приема името на колоната като входен параметър и връща информацията дали тази колона има промени в данните като част от операциите INSERT или UPDATE.
НЕ ЗА РЕПЛИКАЦИЯ свойството може да се използва в DML тригери, за да се избегне задействането им за промени, идващи чрез процеса на репликация.
DML тригерите могат да се създават и с .Net Framework Common Language Runtime (CLR).
Системата DMVsys.triggers съхранява списъка с всички тригери в обхвата на базата данни. Можем да използваме заявката по-долу, за да извлечем подробности за всички DML тригери в база данни:
SELECT *
FROM sys.triggers
WHERE type = 'TR';
Дефинициите на задействане на DML могат да се видят, ако тригерът не е криптиран. Използваме някоя от следните опции:
sys.sql_modules
SELECT OBJECT_SCHEMA_NAME(object_id, db_id()) Schema_name, OBJECT_NAME(object_id) Trigger_Name, definition
FROM sys.sql_modules
WHERE object_id = OBJECT_ID(<trigger_name>);
OBJECT_DEFINITION() функция
SELECT OBJECT_DEFINITION (OBJECT_ID(<trigger_name>)) AS ObjectDefinition;
sp_helptext съхранена процедура
EXEC sp_helptext '<trigger_name>';
Всички възможни DML събития са налични в sys.events маса. Можем да ги видим с помощта на следната заявка:
SELECT *
FROM sys.events;
Синтаксис на DML тригера
CREATE TRIGGER <trigger_name>
ON <schema_name.table_name | schema_name.view_name >
[ WITH <DML_trigger_option> [ ,...n ] ]
{ FOR | AFTER | INSTEAD OF} <event_type>
AS { sql_statement | EXTERNAL NAME <method specifier> }
За демонстрационни цели създадох две таблици, наречени Продажби и История на продажбите с няколко колони в тестовата база данни:
CREATE TABLE Sales (SalesId int IDENTITY NOT NULL, SalesDate datetime, Itemcount int, price money);
CREATE TABLE SalesHistory (SalesId int NOT NULL, SalesDate datetime, Itemcount int, price money, ChangeType varchar(10), ChangeDate datetime DEFAULT GETDATE(), ChangedUser varchar(100) DEFAULT SUSER_NAME());
GO
Както можете да видите, История на продажбите таблицата има 3 допълнителни колони за проследяване на модифицираната дата и потребителско име, които са извикали промяната. Ако е необходимо, можем да имаме още една графа за самоличност дефиниран и го направи и първичен ключ.
Задействане INSERT
Създаваме прост тригер INSERT в Продажби таблици, за да ВМЕСИТЕ всички нови промени в записи в SalesHistory маса. Използвайте следния скрипт:
CREATE TRIGGER TR_INS_Sales ON Sales
FOR INSERT
AS
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'INSERT'
FROM inserted
END
GO
За да обясним синтаксиса на тригера, създадохме DML тригер с име TR_INS_Sales на Продажби маса. Той трябва да задейства тригера само за операциите INSERT – вмъкване на записи в SalesHistory таблица от вмъкнатата таблица.
Както знаем, всмъкнати е логическа таблица, която улавя промените, случващи се в таблицата източник (Продажби таблица в нашия случай).
Можем да видим друга специална логическа таблица, изтрита в тригера UPDATE, защото изтритото таблицата не е приложима за задействания INSERT.
Нека добавим нов запис, за да проверим дали записите са вмъкнати в SalesHistory таблица автоматично.
INSERT INTO Sales(SalesDate,Itemcount,price)
VALUES ('2021-01-01', 5, 100);
Въпреки че сме добавили само един запис в Продажби таблица, получаваме 2 реда от 1 ред засегнат съобщение. Вторият запис се появява поради операцията INSERT като част от тригера, извикан от дейността INSERT в Продажби таблица – вмъкване на запис в SalesHistory таблица.
Нека проверим записите и в двете Продажби и История на продажбите таблици:
SELECT *
FROM Sales
SELECT *
FROM SalesHistory
Можем да видим тази Дата на промяна и ChangedUser се попълват автоматично. Това е така, защото ние проектирахме нашата История таблица със стойностите по подразбиране като GETDATE() и SUSER_NAME() .
Крайните потребители могат да видят чрез Trigger или други средства, че техният INSERT е одитиран чрез допълнителния 1 засегнат ред съобщение. Ако искате да наблюдавате промените, без да информирате потребителите, трябва да приложите ЗАДАВАНЕ НА БРОЙ НА РЕДОВ ON команда. Той потиска резултатите, показвани за DML операции, които се случват вътре в тригера.
Нека ПРОМЕНИМ нашето задействане с помощта на скрипта с ЗАДАВАНЕ НА БРОЙ НА РЕДОВ ON опция и я вижте в действие:
ALTER TRIGGER TR_INS_Sales ON Sales
FOR INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'INSERT'
FROM inserted
END
GO
Сега вмъкваме друг запис в Продажби таблица:
INSERT INTO Sales(SalesDate,Itemcount,price)
VALUES ('2021-02-01', 1, 50);
Можем да видим само един 1 засегнат ред съобщение. По този начин целевата аудитория може изобщо да не бъде уведомена, че действията им са под наблюдение.
Нека проверим дали тригерът INSERT е извикан, като проверим Продажби и История на продажбите таблици.
Да, събитието INSERT в Продажби таблицата беше задействана успешно. Записите бяха вмъкнати в История на продажбите таблица без уведомяване на потребителите.
Следователно, ако създавате задействания за целите на одита, ЗАДАВАНЕ НА NOCOUNT ON необходимо е. Позволява одит, без да предупреждава никого.
Задействане за АКТУАЛИЗИРАНЕ
Преди да създадете действително задействане UPDATE в Продажби таблица, нека отново се обърнем към специалните логически вмъкнати и изтрити таблици. Създайте примерен тригер UPDATE в Продажби таблица:
CREATE TRIGGER TR_UPD_Sales ON Sales
FOR UPDATE
AS
BEGIN
SELECT * FROM inserted
SELECT * FROM deleted
END
GO
Тригерът UPDATE беше създаден успешно. Сега, нека да ВМЕСТИм нов запис неправилно. По-късно ще го актуализираме, за да проверим задействането на UPDATE в действие:
INSERT INTO Sales(SalesDate,Itemcount,price)
VALUES ('2021-02-01', 1, 50);
Имаме записи по-долу в Продажби и История на продажбите таблица:
Нека актуализираме SalesId =3 в Продажби таблица с нови стойности. Ще видим данните във вмъкнатите и изтрити таблици:
UPDATE Sales
SET SalesDate = '2021-03-01'
, Itemcount = 3
, price = 500
WHERE SalesId = 3
Когато се извърши операцията UPDATE, всички нови или променени стойности ще бъдат налични във вмъкнатата таблица, а старите стойности ще бъдат налични в изтритата таблица:
Сега нека променим тригера UPDATE със скрипта по-долу и да го проверим в действие:
ALTER TRIGGER TR_UPD_Sales ON Sales
FOR UPDATE
AS
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'UPDATE'
FROM inserted
END
GO
Тригерът UPDATE беше променен успешно и можем да изпълним отново същия UPDATE скрипт:
Сега можем да видим засегнатия ред съобщение два пъти. Той показва изпълнението на операцията UPDATE на Продажби таблица и операцията INSERT в SalesHistory маса. Нека проверим това, като изберете и в двете таблици:
Активността UPDATE беше проследена в SalesHistory таблица като нов запис. Преди този запис имаме още един, който показва кога записът е бил вмъкнат първи.
DELETE Trigger
Досега тествахме ЗА илиСЛЕД тип тригери както за операции INSERT, така и за UPDATE. Сега можем да опитаме да използваме ВМЕСТО тип DML тригер за операцията DELETE. Използвайте следния скрипт:
CREATE TRIGGER TR_DEL_Sales ON Sales
INSTEAD OF DELETE
AS
BEGIN
RAISERROR ('Notify Sales Team', 16, 10);
END
GO
Тригерът DELETE е създаден успешно. Той ще изпрати съобщение за грешка до клиента, вместо да изпълнява командата DELETE в Продажби таблица.
Нека се опитаме да изтрием запис SalesID =3 от Продажби таблица с помощта на скрипта по-долу:
DELETE FROM Sales
WHERE SalesId = 3
Попречихме на потребителите да изтриват записи от Продажби маса. Тригерът изведе съобщение за грешка.
Нека също така да проверим дали записът е изтрит от Продажби таблица и дали е имало промени в SalesHistory таблица:
Тъй като сме изпълнили скрипта за задействане преди действителния оператор DELETE, използвайки тригер INSTEAD OF, операцията DELETE на SalesId=3 изобщо не беше успешна. Следователно не бяха отразени промени и в двете Продажби и История на продажбите таблица.
Нека модифицираме тригера с помощта на скрипта по-долу, за да идентифицираме опита за ИЗТРИВАНЕ в таблицата:
ALTER TRIGGER TR_DEL_Sales ON Sales
INSTEAD OF DELETE
AS
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'DELETE ATP'
FROM deleted
END
GO
Спусъкът е променен успешно. Нека изтрием SalesId =3 запис от Продажби отново таблица:
Изпълнението на оператора DELETE показва 1 засегнат ред съобщение два пъти. Нека проверим записите в Продажби и История на продажбите таблици, за да видите какво точно се случва там:
Логиката, използвана в тригера DELETE, беше да улавя всички опити за ИЗТРИВАНЕ на масата, без всъщност да изтрива записа от Продажби таблица с помощта на ВМЕСТО задействане. Можем да потвърдим, че записът не е изтрит от Продажби таблица и беше вмъкнат нов запис в SalesHistory таблица.
Единичен тригер за обработка на операции INSERT, UPDATE и DELETE
Досега създадохме 3 тригера за обработка на операциите INSERT, UPDATE и DELETE в една таблица. Ако имаме множество задействания, би било трудно да ги управляваме, особено ако не са правилно документирани. Може да има проблеми с производителността, ако разработчиците са използвали противоречива логика в множество задействания.
Аз лично препоръчвам да използвате единичен тригер с комбинирана логика, за да избегнете потенциална загуба на данни или проблеми с производителността. Можем да опитаме да комбинираме 3 тригера в един тригер за по-добра производителност. Но преди да го направим, нека да разгледаме как да ОТПУСКАМЕ съществуващите тригери и как да деактивираме или активираме тригери.
Пуснете спусъка
За да обединим 3 задействания в едно, първо трябва да ИЗПУСКАМЕ тези 3 тригера. Възможно е както чрез SSMS, така и чрез T-SQL подходи.
В SSMS разгънете Тест база данни > Таблици > Продажби таблица> Задействания .
Можем да видим нашите 3 тригера, създадени досега:
За да пуснете задействане, просто щракнете с десния бутон върху него> Изтриване > ОК .
Ако предпочитате да използвате T-SQL, вижте синтаксиса по-долу, за да премахнете тригера:
DROP TRIGGER <trigger_name>
Има TR_INS_Sales тригер, който създадохме в Продажби маса. Скриптът ще бъде:
DROP TRIGGER TR_INS_Sales
Важно :Отпадането на таблица отпада всички задействания по подразбиране.
Деактивирайте и активирайте тригера
Вместо да пускаме спусъка, можем да го деактивираме временно с Деактивиране спусък опция чрез SSMS или T-SQL.
В SSMS щракнете с десния бутон върху Име на тригера> Деактивиране . Веднъж деактивиран, тригерът няма да се задейства, докато не го активирате обратно.
Докато тригерът работи, Активиране опцията е оцветена в сиво. Когато го деактивирате, Активиране опцията ще стане видима и активна.
Ако предпочитате да използвате T-SQL, можете да деактивирате и активирате тригери, като използвате следните скриптове:
-- To Disable all triggers on a specific table
DISABLE TRIGGER ALL ON <table_name>;
-- To Disable a specific trigger on a table
DISABLE TRIGGER <trigger_name> ON <table_name>;
-- To Enable all triggers on a specific table
ENABLE TRIGGER ALL ON <table_name>;
-- To Enable a specific trigger on a table
ENABLE TRIGGER <trigger_name> ON <table_name>;
За да деактивирате и активирате нашите конкретни TR_INS_Sales задействане на Продажби таблица, използваме следните скриптове:
-- To Disable TR_INS_Sales trigger on Sales table
DISABLE TRIGGER TR_INS_Sales ON Sales;
-- To Enable TR_INS_Sales trigger on Sales table
ENABLE TRIGGER TR_INS_Sales ON Sales;
Така се научихме как даИЗПУСКАМЕ ,ИЗКЛЮЧВАНЕ , и АКТИВИ тригери. Ще премахна 3 съществуващи задействания и ще създам едно задействане, покриващо всички 3 операции или вмъкване, актуализиране и изтриване с помощта на скрипта по-долу:
DROP TRIGGER TR_INS_Sales
DROP TRIGGER TR_UPD_Sales
DROP TRIGGER TR_DEL_Sales
GO
CREATE TRIGGER TR_INS_UPD_DEL_Sales ON Sales
FOR INSERT, UPDATE, DELETE
AS
BEGIN
IF (SELECT COUNT (*) FROM deleted) = 0
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'INSERT'
FROM inserted
END
ELSE IF (SELECT COUNT (*) FROM inserted) = 0
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'DELETE'
FROM deleted
END
ELSE IF (UPDATE (SalesDate) OR UPDATE (ItemCount) OR UPDATE (Price))
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'UPDATE'
FROM inserted
END
END
GO
Създаването на единичен тригер беше успешно. Използвахме логика, за да идентифицираме операцията, като използваме вмъкнатите и изтрити таблици.
За операцията INSERT изтритата таблица няма да бъде попълнена. За операцията DELETE вмъкнатата таблица няма да бъде попълнена. Можем лесно да идентифицираме тези операции. Ако тези 2 условия не съвпадат, това е операция UPDATE и можем да използваме обикновен оператор ELSE.
Използвах UPDATE() функция, за да покаже как работи. Ако имаше някакви актуализации за тези колони, действието на задействане UPDATE ще се задейства. Можем също да използваме COLUMNS_UPDATED() функция, която обсъдихме по-рано, за да идентифицира и операция UPDATE.
Нека тестваме нашия нов тригер, като вмъкнем нов запис:
INSERT INTO Sales(SalesDate,Itemcount,price)
VALUES ('2021-04-01', 4, 400);
Проверка на записи в Продажби и История на продажбите таблици показват данните, както следва:
Нека опитаме да актуализираме SalesId =2 запис:
UPDATE Sales
SET price = 250
WHERE SalesId = 2;
Нека опитаме DELETE скрипт чрез тази процедура на SalesId =4 запис:
DELETE FROM Sales
WHERE SalesId = 4;
Както можем да забележим, SalesId =4 беше изтрит от Продажби таблица, тъй като това е ЗА или СЛЕД тригер, което кара операцията DELETE да успее при Продажби таблица и след това вмъкнете запис в SalesHistory таблица.
Цел на DML тригери
DML тригерите служат ефективно за следните сценарии:
- Проследявайте исторически промени на операции INSERT, UPDATE и DELETE в конкретна таблица.
- Одитирайте DML събитията, случващи се на таблица, без да излагате одитната дейност на потребителите.
- Предотвратете извършването на промени в DML на таблица чрез ВМЕСТО задейства и предупреждава потребителите с конкретно съобщение за грешка.
- Изпращайте известия до целеви хора при постигане на предварително определени условия.
- Стартирайте заданието на агент на SQL Server или всеки друг процес, когато постигнете предварително определени условия.
И можете да ги използвате за всякакви други изисквания на бизнес логиката, които можете да приложите с T-SQL изрази.
Заключение
Тялото на DML тригера е подобно на съхранената процедура. Можем да приложим всяка необходима бизнес логика, но трябва да бъдем внимателни, докато пишем тази логика, за да избегнем потенциални проблеми.
Въпреки че SQL Server поддържа създаването на множество тригери в една таблица, по-добре е да се консолидира в един тригер. По този начин можете лесно да поддържате тригери и да ги отстранявате по-бързо. Всеки път, когато задействанията на DML се внедряват за целите на одита, уверете се, че SET NOCOUNT ON опцията се използва ефективно.
В следващата статия ще разгледаме DDL тригерите и тригерите за влизане.