/*
Introduction to Triggers
*/
---- Creating trigger
-- Create a new trigger that fires when deleting data
create TRIGGER PreventDiscountsDelete
ON Discounts
-- The trigger should fire instead of DELETE
instead of DELETE
AS
PRINT 'You are not allowed to delete data from the Discounts table.';
---- Practicing creating triggers
-- Set up a new trigger
create TRIGGER OrdersUpdatedRows
ON Orders
-- The trigger should fire after UPDATE statements
after UPDATE
-- Add the AS keyword before the trigger body
AS
-- Insert details about the changes to a dedicated table
insert INTO OrdersUpdate(OrderID, OrderDate, ModifyDate)
SELECT OrderID, OrderDate, GETDATE()
FROM inserted;
---- Creating a trigger to keep track of data changes
-- Create a new trigger
CREATE TRIGGER ProductsNewItems
ON Products
AFTER insert
AS
-- Add details to the history table
INSERT INTO ProductsHistory(Product, Price, Currency, FirstAdded)
SELECT Product, Price, Currency, GETDATE()
FROM inserted;
---- Triggers vs. stored procedures
-- Run an update for some of the discounts
update Discounts
SET Discount = Discount + 1
WHERE Discount <= 5;
-- Verify the trigger ran successfully
select * FROM DiscountsHistory;
---- Triggers vs. computed columns
-- Add the following rows to the table
INSERT INTO SalesWithoutPrice (Customer, Product, Currency, Quantity)
VALUES ('Fruit Mag', 'Pomelo', 'USD', 200),
('VitaFruit', 'Avocado', 'USD', 400),
('Tasty Fruits', 'Blackcurrant', 'USD', 1100),
('Health Mag', 'Kiwi', 'USD', 100),
('eShop', 'Plum', 'USD', 500);
-- Verify the results after the INSERT
SELECT * FROM SalesWithoutPrice;
/*
Classification of Triggers
*/
---- Tracking retired products
-- Create the trigger
CREATE TRIGGER TrackRetiredProducts
ON Products
AFTER DELETE
AS
INSERT INTO RetiredProducts (Product, Measure)
SELECT Product, Measure
FROM deleted;
---- Tracking retired products
-- Create the trigger
CREATE TRIGGER TrackRetiredProducts
ON Products
AFTER DELETE
AS
INSERT INTO RetiredProducts (Product, Measure)
SELECT Product, Measure
FROM deleted;
---- The TrackRetiredProducts trigger in action
-- Remove the products that will be retired
DELETE FROM Products
WHERE Product IN ('Cloudberry', 'Guava', 'Nance', 'Yuzu');
-- Verify the output of the history table
SELECT * FROM RetiredProducts;
---- Practicing with AFTER triggers
-- Create a new trigger for canceled orders
CREATE TRIGGER KeepCanceledOrders
ON Orders
AFTER DELETE
AS
INSERT INTO CanceledOrders
SELECT * FROM deleted;
-- Create a new trigger to keep track of discounts
CREATE TRIGGER CustomerDiscountHistory
ON Discounts
AFTER INSERT
AS
-- Store old and new values into the `DiscountsHistory` table
INSERT INTO DiscountsHistory (Customer, OldDiscount, NewDiscount, ChangeDate)
SELECT i.Customer, d.Discount, i.Discount, GETDATE()
FROM inserted AS i
INNER JOIN deleted AS d ON i.Customer = d.Customer;
-- Notify the Sales team of new orders
CREATE TRIGGER NewOrderAlert
ON Orders
AFTER INSERT
AS
EXECUTE SendEmailtoSales;
---- Preventing changes to orders
-- Create the trigger
CREATE TRIGGER PreventOrdersUpdate
ON Orders
INSTEAD OF UPDATE
AS
RAISERROR ('Updates on "Orders" table are not permitted.
Place a new order to add new products.', 16, 1);
---- Creating the PreventNewDiscounts trigger
-- Create a new trigger
CREATE TRIGGER PreventNewDiscounts
ON Discounts
INSTEAD OF INSERT
AS
RAISERROR ('You are not allowed to add discounts for existing customers.
Contact the Sales Manager for more details.', 16, 1);
---- Tracking table changes
-- Create the trigger to log table info
CREATE TRIGGER TrackTableChanges
ON DATABASE
FOR CREATE_TABLE,
ALTER_TABLE,
DROP_TABLE
AS
INSERT INTO TablesChangeLog (EventData, ChangedBy)
VALUES (EVENTDATA(), USER);
---- Preventing table deletion
-- Add a trigger to disable the removal of tables
CREATE TRIGGER PreventTableDeletion
ON DATABASE
FOR DROP_TABLE
AS
RAISERROR ('You are not allowed to remove tables from this database.', 16,
1);
-- Revert the statement that removes the table
ROLLBACK;
---- Enhancing database security
-- Save user details in the audit table
INSERT INTO ServerLogonLog (LoginName, LoginDate, SessionID, SourceIPAddress)
SELECT ORIGINAL_LOGIN(), GETDATE(), @@SPID, client_net_address
-- The user details can be found in SYS.DM_EXEC_CONNECTIONS
FROM SYS.DM_EXEC_CONNECTIONS WHERE session_id = @@SPID;
/*
Trigger Limitations and Use Cases
*/
---- Creating a report on existing triggers
-- Gather information about database triggers
SELECT name AS TriggerName,
parent_class_desc AS TriggerType,
create_date AS CreateDate,
modify_date AS LastModifiedDate,
is_disabled AS Disabled,
is_instead_of_trigger AS InsteadOfTrigger,
-- Get the trigger definition by using a function
OBJECT_DEFINITION (object_id)
FROM sys.triggers
UNION ALL
-- Gather information about server triggers
SELECT name AS TriggerName,
parent_class_desc AS TriggerType,
create_date AS CreateDate,
modify_date AS LastModifiedDate,
is_disabled AS Disabled,
0 AS InsteadOfTrigger,
-- Get the trigger definition by using a function
OBJECT_DEFINITION (object_id)
FROM sys.server_triggers
ORDER BY TriggerName;
---- Keeping a history of row changes
-- Create a trigger to keep row history
CREATE TRIGGER CopyCustomersToHistory
ON Customers
-- Fire the trigger for new and updated rows
AFTER INSERT, UPDATE
AS
INSERT INTO CustomersHistory (CustomerID, Customer, ContractID, ContractDate,
Address, PhoneNo, Email, ChangeDate)
SELECT CustomerID, Customer, ContractID, ContractDate, Address, PhoneNo,
Email, GETDATE()
-- Get info from the special table that keeps new rows
FROM inserted;
---- Table auditing using triggers
-- Add a trigger that tracks table changes
create trigger OrdersAudit
ON Orders
after INSERT, update, delete
AS
DECLARE @Insert BIT = 0;
DECLARE @Delete BIT = 0;
IF EXISTS (SELECT * FROM inserted) SET @Insert = 1;
IF EXISTS (SELECT * FROM deleted) SET @Delete = 1;
INSERT INTO TablesAudit (TableName, EventType, UserAccount, EventDate)
SELECT 'Orders' AS TableName
,CASE WHEN @Insert = 1 AND @Delete = 0 THEN 'INSERT'
WHEN @Insert = 1 AND @Delete = 1 THEN 'UPDATE'
WHEN @Insert = 0 AND @Delete = 1 THEN 'DELETE'
END AS Event
,ORIGINAL_LOGIN() AS UserAccount
,GETDATE() AS EventDate;
---- Preventing changes to Products
-- Prevent any product changes
CREATE TRIGGER PreventProductChanges
ON Products
INSTEAD OF UPDATE
AS
RAISERROR ('Updates of products are not permitted. Contact the database
administrator if a change is needed.', 16, 1);
---- Checking stock before placing orders
-- Create a new trigger to confirm stock before ordering
CREATE TRIGGER ConfirmStock
ON Orders
INSTEAD OF INSERT
AS
IF EXISTS (SELECT *
FROM Products AS p
INNER JOIN inserted AS i ON i.Product = p.Product
WHERE p.Quantity < i.Quantity)
BEGIN
RAISERROR ('You cannot place orders when there is no stock for the
order''s product.', 16, 1);
END
ELSE
BEGIN
INSERT INTO Orders (OrderID, Customer, Product, Price, Currency,
Quantity, WithDiscount, Discount, OrderDate, TotalAmount, Dispatched)
SELECT OrderID, Customer, Product, Price, Currency, Quantity,
WithDiscount, Discount, OrderDate, TotalAmount, Dispatched FROM INSERTED;
END;
---- Database auditing
-- Create a new trigger
CREATE TRIGGER DatabaseAudit
-- Attach the trigger at the database level
ON DATABASE
-- Fire the trigger for all tables/ views events
FOR DDL_TABLE_VIEW_EVENTS
AS
INSERT INTO DatabaseAudit (EventType, DatabaseName, SchemaName, Object,
ObjectType, UserAccount, Query, EventTime)
SELECT EVENTDATA().value('(/EVENT_INSTANCE/EventType)[1]', 'NVARCHAR(50)') AS
EventType
,EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]',
'NVARCHAR(50)') AS DatabaseName
,EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(50)')
AS SchemaName
,EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]',
'NVARCHAR(100)') AS Object
,EVENTDATA().value('(/EVENT_INSTANCE/ObjectType)[1]', 'NVARCHAR(50)')
AS ObjectType
,EVENTDATA().value('(/EVENT_INSTANCE/LoginName)[1]', 'NVARCHAR(100)')
AS UserAccount
,EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]',
'NVARCHAR(MAX)') AS Query
,EVENTDATA().value('(/EVENT_INSTANCE/PostTime)[1]', 'DATETIME') AS
EventTime;
---- Preventing server changes
-- Create a trigger to prevent database deletion
CREATE TRIGGER PreventDatabaseDelete
-- Attach the trigger at the server level
ON ALL SERVER
FOR DROP_DATABASE
AS
PRINT 'You are not allowed to remove existing databases.';
ROLLBACK;
/*
Trigger Optimization and Management
*/
---- Removing unwanted triggers
-- Remove the trigger
DROP TRIGGER PreventNewDiscounts;
-- Remove the database trigger
DROP TRIGGER PreventTableDeletion
ON DATABASE;
-- Remove the server trigger
DROP TRIGGER DisallowLinkedServers
ON ALL SERVER;
---- Modifying a trigger's definition
-- Fix the typo in the trigger message
ALTER TRIGGER PreventDiscountsDelete
ON Discounts
INSTEAD OF DELETE
AS
PRINT 'You are not ALLOWED to remove data from the Discounts table.';
---- Disabling a trigger
-- Pause the trigger execution
DISABLE TRIGGER PreventOrdersUpdate
ON ORDERS;
---- Re-enabling a disabled trigger
-- Resume the trigger execution
ENABLE TRIGGER PreventOrdersUpdate
ON ORDERS;
---- Managing existing triggers
-- Get the disabled triggers
SELECT name,
object_id,
parent_class_desc
FROM sys.triggers
WHERE is_disabled = 1;
-- Check for unchanged server triggers
SELECT *
FROM sys.SERVER_TRIGGERS
WHERE modify_date = create_date;
-- Get the database triggers
SELECT *
FROM sys.TRIGGERS
WHERE parent_class_desc = 'DATABASE';
---- Keeping track of trigger executions
-- Modify the trigger to add new functionality
ALTER TRIGGER PreventOrdersUpdate
ON Orders
-- Prevent any row changes
INSTEAD OF UPDATE
AS
-- Keep history of trigger executions
INSERT INTO TriggerAudit (TriggerName, ExecutionDate)
SELECT 'PreventOrdersUpdate',
GETDATE();
RAISERROR ('Updates on "Orders" table are not permitted.
Place a new order to add new products.', 16, 1);
---- Identifying problematic triggers
-- Get the table ID
SELECT object_id AS TableID
FROM sys.objects
WHERE name = 'Orders';
-- Get the trigger name
SELECT t.name AS TriggerName
FROM sys.objects AS o
-- Join with the triggers table
INNER JOIN sys.triggers AS t ON t.parent_id = o.object_id
WHERE o.name = 'Orders';
SELECT t.name AS TriggerName
FROM sys.objects AS o
INNER JOIN sys.triggers AS t ON t.parent_id = o.object_id
-- Get the trigger events
INNER JOIN sys.trigger_events AS te ON te.object_Id = t.object_Id
WHERE o.name = 'Orders'
-- Filter for triggers reacting to new rows
AND te.type_desc = 'UPDATE';
SELECT t.name AS TriggerName,
OBJECT_DEFINITION(t.object_id) AS TriggerDefinition
FROM sys.objects AS o
INNER JOIN sys.triggers AS t ON t.parent_id = o.object_id
INNER JOIN sys.trigger_events AS te ON te.object_id = t.object_id
WHERE o.name = 'Orders'
AND te.type_desc = 'UPDATE';