Building and Optimizing Triggers
Introduction to Triggers
Classification of Triggers
Trigger Limitations and Use Cases
Trigger Optimization and Management
Introduction to Triggers
Triggers
A special type of Stored Procedure that is automatically executed when events like (data modifications) occur on the database server
Types of Triggers: In T-sql server there are 3 main types of triggers:
Data Manipulation Language (DML) triggers: When a user or process modififes data through an INSERT, UPDATE, or DELETE thesee triggers are associated with statements related to tables or views
Data Definition Language: fire in response to statements executed at the database or server level, like
CREATE
,ALTER
orDROP
Logon triggers: fire in response to LOGON events when a user's session is established.
Another way to classify triggers is to classify them based on their behaviour.
Types of Trigger (based on behaviour)

A trigger can behave differently in relation to te statement that fires it, resulting in two types of triggers
AFTER
Trigger: when you want to execute a piece of code after the initial statement that fires the trigger, example of this triggers: a simple use case of this type of trigger is to rebuild an index after a large insert of data into a table. another example: is using a trigger to send alerts when UPDATE statements are run againse the database. (Notify the admin when data is updated)INSTEAD OF
Trigger:
will not perfome the inital opertation, but will execute custom code instead, so a replacement statement is executed instead of the original one some examples (use cases):
prevent insertions: prevent inserting data into tables,
prevent updated or deletion.
Prevent object modification.
and you can also notify the database administrarotr of suspicous behaviour while also preventing any changes
Trigger Definition
because trigger is a sql server object, we add a new one by using the CREATE statement.
General Syntax
CREATE TRIGGER <TriggerName>
ON <Target>
<Trigger_type> <event>
AS
{your_trigger_action}
You should give the trigger a descriptive name For example an trigger created with AFTER
-- Create a trigger by giving it a descriptie name
CREATE TRIGGER ProductTrigger
--The Trigger neeeds to be attachred to a table
ON Products
-- The Trigger Behvaiour type
AFTER INSERT
--The Beginning of the trigger workflow
AS
-- The action executed by the trigger
PRINT('An insert of data was made in the products table')
(with INSTEAD OF)
CREATE TRIGGER PreventDeleteFromOrders
ON Orders
INSTEAD OF DELETE
AS
PRINT('You are not allowed to delete rows from the Orders table.')
any attempt to remove rows from the table will fail due the use of INSTEAD OF
Practical Example\
-- 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 * The Fresh Fruit Delivery company needs help creating a new trigger called OrdersUpdatedRows on the Orders table.
This trigger will be responsible for filling in a historical table (OrdersUpdate) where information about the updated rows is kept.
A historical table is often used in practice to store information that has been altered in the original table. In this example, changes to orders will be saved into OrdersUpdate to be used by the company for auditing purposes.
-- 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;

How DML Triggers are used
Initiating actions when manipulating data: the main reason for using DML triggers is to initiate actions when manipulating data
preventing data manipulation: and sometimes the manipulation of data needs to be prevented, and this can also be done with the use of trigger
Tracking data or database object changes: another powerful use case often seen in practice is using the triggers for tracking data change and even database object changes
User auditing and database security: database admins also use triggers to track user action unwanted changes and to secure the database by protecting it from
How to decide between AFTER and INSTEAD OF? each one of them has certain use cases, so depending the on use case your choice will be fair AFTER: one good example of using AFTER trigger is for a large insert of data into a sales table, once the data gets inserted, a cleaning step should run to remove or repaint any unwanted information when the cleansing step finished, a report with the results will be generated, this report should be anaylzed by a database adminstrator so the trigger will then send an email to the responsible people.
CREATE TRIGGER SalesNewInfoTrigger
ON sales
AFTER INSERT
AS
EXEC sp_cleansing @Table = 'Sales'
EXEC sp_generateSalesReport;
EXEC sp_sendnotification;
INSTEAD OF a good use case:
Imagine you have a table containing light bulb information and stock details for a sales platform
x
Standford
30W
30
y
Buma
40W
0
z
Ultra
50W
0
the "Power" column values have changed for some models, and an update is initiated to change the information in the table but there are still some bulb models in stock that have the old power value, however, and they shouldn't be updated so Instead of
trigger can help you deal with this more complex situation the correct approach is to update the characterstics only for the models that don't have old version in stock; the new models need to be in the table too, but as new rows instead of updated ones
CREATE TRIGGER BulbStockTrigger
ON Bulbs
INSTEAD OF INSERT
AS
IF EXISTS (SELECT * FROM Bulbs As b
INNER JOIN inserted AS i
ON b.Brand = i.Brand
AND b.Model = i.Model
Where b.Stock = 0)
BEGIN
UPDATE b
SET b.Power = i.Power;
b.Stock = i.Stock;
FROM Bulbs As b
INNER JOIN inserted AS i
ON a.Brand = i.Brand;
And b.Model = i.Model
WHERE b.Stock = 0
END
ELSE
INSERT INTO Bulbs
SELECT * FROM inserted;
Power changes from some models
Update only the products with not stock.
Add new rows for the products with stock
the new modes need to be in the table too, but as new rows instead of updated ones.
x
Standford
30W
30
y
Buma
40W
100
z
Ultra
50W
100
x
Standford
35w
100
Practical Example
CREATE TRIGGER ProductsNewItems
ON Products
AFTER INSERT
AS
INSERT INTO ProductsHistory(Product, Price, Currency, FirstAdded)
SELECT Product, Price, Currency, GETDATE()
FROM inserted;
Trigger Alternative
Trigger are great, but they are not the only solution in SQL server So we will discuss some alternatives
Triggers vs Stored Procedures Triggers as you know are special kind of Stored Procedure, but what makes them "Special"? Triggers are fired automatically by an event
-- Will fire an Insert trigger
INSERT INTO Orders [...];
In opposite direction, SP run only when called explicitly
-- will run the sp
EXECUTE sp_DailyMaintaince
Triggers:
don't allow parameters or transactions.
can't return values as output; SP : Accept input parameters and transcations can return values as output so these differences enforce some use cases for each one of them
Triggers used for:
auditing
Integrity enforcement
SP used for:
general tasks
user-specific needs
the second comparison is with Computed Columns Triggers v Computed columns Computed Columns: are a good way to automate the calculation of the values contained by some columns. Triggers:
use columns from other tables for calculations Computed Columns:
use columns only from the same table for calculations
while this calculation will be done with INSERT or UPDATE statments when using a trigger,
--used in the trigger body
[...]
UPDATE
SET TotalAmount = Price * Quantity
[...]
for a computed column it will be part of the table definition
-- Column Definition
[...]
TotalAmount As Price * Quantity
[...]
Example of Computed Column As you see in the definintion of SalesWithPrice, the 'TotalAmount' table value comes from the mutliplication of the 'Quantity' and 'price' column from the same table
CREATE TABLE [SalesWithPrice]
(
[OrderID] INT INDENTITY(1,1),
[Customer] NVARCHAR(50),
[Product] NVARCHAR(50),
[Price] DECIMAL(10, 2),
[Currency] NVARCHAR(3),
[Quantity] INT,
[OrderDate] DATE DEFAULT (GETDATE()),
[TotalAmount AS [Quantity] * [Price]
)
But if those two columns are on other tables, we can't use Computed Columns, we use trigger instead the Price column is not part of the 'SalesWitoutPrice' table
CREATE TRIGGER [SalesCalculateTotalAmount]
On [SalesWithoutPrice]
AFTER INSERT
AS
UPDATE [sp]
SET [sp].[TotalAmount] = [sp].[Quantity] * [p].[Price]
FROM [SalesWithoutPrice] AS [sp]
INNER JOIN [Products] AS [p] ON [sp].Product = [p].[Product]
WHERE [sp].[TotalAmount] IS NULL;
Classifications of Triggers in Depth (DML)
AFTER TRIGGERs
After trigger can be used for both DML and DDL statment we'll be focusing on DML AFTER triggers the trigger will be fired ""AFTER" the event finish executing not at the beginning of the event
AFTER Trigger prerequisites:
Table or view needed for DML statements
The Trigger will be attached to the same table so we we need: [1] Target [2] Description of the trigger: what you are tryint to achieve with the trigger let's take an example: in this example we want to keep some details of products that are not sold anymore, these products will be removed from the Product table, but their details will be kept in a 'ReturedProducts' table for financial accounting reasons so out trigger will save information about deleted rows (from product table) to the 'RetiredProduct' table the trugger should hae a uniquely identify name for our example it will be 'TrackRetiredProducts' wheneveer rows are deleteed form the 'Products' table the deleted rows' information will be saved to to the 'RetiredProducts'
CREATE TRIGGER TrackRetiredProducts
ON Products
AFTER DELETE
AS
INSERT INTO RetiredProducts(Product, Measure)
SELECT Product, Measure
FROM deleted;
notice that we are not getting the information from the 'Products' table, but from a table alled' deleted'
"insterted" and "deleted"tables
these tables are automatically created by SQL server and you can make use of them in your trigger actions. Depending on the operation you are performing, they will hold different inormation
[1] Inserted table this table will store the values of the new rows for "INSERT" and "UPDATE" statements
inserted
new rows
new rows
N/A
deleted
N/A
updated rows
removed rows
[2] deleted table: will store the value of modified rows for update statement, or the value of removed rows for 'DELETE' statment
time for exercise
-- Create the trigger
CREATE trigger TrackRetiredProducts
ON Product
AFTER DELETE
AS
INSERT INTO RetiredProducts (Product, Measure)
SELECT Product, Measure
FROM deleted;
To ensure
-- 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 Fresh Fruit Delivery company is happy with your services, and they've decided to keep working with you.
You have been given the task to create new triggers on some tables, with the following requirements:
[1] Keep track of canceled orders (rows deleted from the Orders table). Their details will be kept in the table CanceledOrders upon removal.
[2] Keep track of discount changes in the table Discounts. Both the old and the new values will be copied to the DiscountsHistory table.
[3] Send an email to the Sales team via the SendEmailtoSales stored procedure when a new order is placed.
CREATE TRIGGER KeepCanceledOrders
ON Orders
AFTER DELETE
AS
INSERT INTO CanceledOrders ()
-- Create a new trigger to keep track of discounts
CREATE Trigger CustomerDiscountHistory
ON Discounts
AFTER UPDATE
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;
INSTEAD OF Trigger
in contrast with AFTER triggers, INSERT OF triggers can only be used for DML statements (not DDL)
the actions are performerd instead of the DML event
The DML event does not run anymore
let's take an example our example is a table Orders which holds the details of the orders placed by the Fresh Fruit Delivery Company's customer <img src"#" alt="Table of Orders> as we discussed before, you should keep on mind some steps to know exactly what your trigger will be doing. [1] Target Table -> Orders [2] Description of the trigger -> the trigger we will create should prevent updates to the existing entries in this table, this will ensure that placed orders cannot be modified, this is a rule enforced by the company, this means our trigger will fire as a response to UPDATE statements [3] Trigger firing event (DML) -> UPDATE [4] Trigger Name -> PreventOrdersUpdate (having an infomarie name is important when creating triggers) let's create the triggers
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);
Exercise Time:
```sql
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);
Attempting to UPDATE will throw an error
UPDATE Orders SET Quantity = 700
where OrderID = 425;
DDL Triggers
Only used with AFTER
and attached to only database or server level
no special tables
AFTER and FOR
the FOR and AFTER have the same results
CREATE TRIGGER Database ChangeLog
FOR CREATE_TABLE
[...]
Developers tend to use AFTER for DML triggers and FOR keyword for DDL triggers
DDL Trigger preprequisites
[1] Target Object (server or database) =>DATABASe [2] Description of the trigger => Log table with definition changes [3] Trigger firing events (DDL) => CREATE_TABLE, ALTER_TABLE, DROP_TABLE [4] Trigger Name => TrackTableChanges
CREATE TRIGGER TrackTableChanges
ON DATABASe
FOR CREATE_TABLE,ALTER_TABLE, DROP_TABLE
AS
INSERT INTO TablesChangesLog(EventData, ChangedBy)
VALUES(EVENTDATA(), USER)
EVENTDATA() function: holds information about the event that runs and fires the trigger
Preventing the triggering event for DML triggers
we've discussed before that we can't use INSTEAD OF with DDL Triggers!! this means we don't have a solution around this if we want to do similar thing with DDL triggers? The Answer is No, we have we can defone a trigger to roll back the statements that fired it
CREATE TRIGGER PreventTableDeletion
ON DATABASE
FOR DROP_TABLE
AS
RAISERROR('You are not allowed to remove tables from this database.', 16, 1);
ROLLBACK;
LOGON Triggers
are fired when a user logs on and creates a connection to a SQL server. the trigger is fired after the authentication phase (meaning after the username and password are checked), BUT before the user session is established (when the information from SQL Server becomes available for queries)
Logon trigger prerequisites
logon trigger can only be attached at the server level, and the firing event can only be LOGON
[1] Trigger firing event -> LOGON [2] Description of the trigger -> Audit successful / failed logons to the server [3] Trigger name -> LogonAudit the trigger will be executed nder the same credentials (username and password) as the firing event.
CREATE TRIGGER LogonAudit
ON ALL SERVER WITH EXECUTE AS 'sa'
FOR LOGON
AS
INSERT INTO ServerLogonLog(LoginName, LoginDate, SessionID, SourceIPAddress)
SELECT ORIGNAL_LOGIN(), GETDATE(), @@SPID, client_net_address
FROM SYS.DM_EXEC_CONNECTIONS WHERE session_id = @@SPID;
'sa' is a built-in adminstrator account tht has the full permissions on the server, because regular users don't have sensitive information like logon details
@@SIPD => the id of the current user
some exercises
-- 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;
-- Create a trigger firing when users log on to the server
CREATE TRIGGER LogonAudit
-- Use ALL SERVER to create a server-level trigger
ON ALL SERVER WITH EXECUTE AS 'sa'
-- The trigger should fire after a logon
for logon
AS
-- Save user details in the audit table
INSERT INTO ServerLogonLog (LoginName, LoginDate, SessionID, SourceIPAddress)
SELECT ORIGINAL_LOGIN(), GETDATE(), @@SPID, client_net_address
FROM SYS.DM_EXEC_CONNECTIONS WHERE session_id = @@SPID;
Trigger limitation and use cases
Let's begin by discussing some of the advantages of using Triggers:
Used for Database Integrity purposes
We can enforce business rules and store it directly in the databases, this makes it easier to change and update the applications that are using the database, because the buisness login is encapsulated inside the database itself
Triggers give us control over which statements are allowed in a database
Implementation of complex business logic triggered by a single event
Auditing the database and user activites
now let's see what the cons are:
Triggers are not easy to manage in centralized manner, because they are difficult to be detected or viewed
they are invisible to client application or when debugging code
because of their complex code, we can't sometime trace their logic when troubleshooting
They can affect the server and make it slower by overusing them
Finding the server-level trigger
From all those pros and cons, we can conclude that we should document our triggers and make them as simpler as we can. Because Triggers can be implemented on many levels (system, database, tables, etc) SQL Server gave us a way to view that information about triggers in one place
[1] Getting system level triggers
SELECT * FROM sys.server_triggers;
[2] Getting the datatabase and table triggers
SELECT * FROM sys.triggers
Trigger type and definition
**The type of the trigger (database or table) can be determined from the 'parent_class_desc' column ** We can view triggers definition graphically using MS management studio: head over to the Triggers folder and right-click on the trigger name and choose 'Script Trigger as -> CREATE TO -> New Query Edit Window' ** SQL system views are like virtual tables in the database, helping to reach the information that cannot be reached otherwise
SELECT definition
FROM sys.sql_modules
WHERE object_id = OBJECT_ID('PreventOrdersUpdate')
** We can also use the OBJECT_DEFINITION() function and pass it the id of the trigger
SELECT OBJECT_DEFINITION(OBJECT_ID('PreventOrdersUpdate')
** the last option we can use to use 'sp_helptext' procedure, which uses a parameter called 'objname'
EXECUTE sp_helptext @objname = 'PreventOrdersUpdate'
but this option is not the most common one, it's rarely used
Triggers best practice
Make sure your database is well-documented
keep your trigger logic simple
avoid overusing triggers
time to practice
-- 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
FROM sys.triggers
UNION ALL
SELECT name AS TriggerName,
-- Get the column that contains the trigger type
parent_class_desc AS TriggerType,
create_date AS CreateDate,
modify_date AS LastModifiedDate,
is_disabled AS Disabled,
0 AS InsteadOfTrigger
-- Gather information about server triggers
FROM sys.server_triggers
-- Order the results by the trigger name
ORDER BY TriggerName;
using OBJECT_DEFINITION
-- 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;
Use cases for AFTER triggers (DML)
A common use for AFTER triggers is to store historical data in other tables (Having a history of changes performed on a table) ** Best practice: Keep an overview of the changes for the most important tables in your database
For example, let's say the customer in Customers table change his phone number, so we keep this change as well as the the old phone number on the 'CustomersHistory' table
The previous table is obtained using a **AFTER** Trigger
CRETE TRIGGER CopyCusomtersToHistory
ON Customers
AFTER INSERT, UPDATE
AS
INSERT INTO CustomersHistroy(Customer, ContractId, Address, PhoneNo)
SELECT Customer, ContractID, Address, PhoneNo, GETDATE()
FROM inserted;
Table auditing using triggers
Another major use of AFTER triggers is to audit changes occurring in the database Auditing means: Tracking any changes that occur within the defined scope usually, the scope of the audit is comprised of very important tables from the database In the following query, we create a trigger called 'OrderAudit' that keep track of any changes occur to the 'Orders Table' it wil fire for any DML statements, inside the trigger we've two Boolean variables that will check the special tables "inserted" and "deleted", when on of the special tables contains data, the associated variables will set to "true" the combination of variables will tell us of the operation is an INSERT, UPDATE, or DELETE these changes will be kept inside 'TablesAudit' Table
CREATE TRIGGER OrdersAudit
ON Orders
AFTER UPDATE, INSERT, DELETE
AS
DECLARE @Insert BIT = 0, @Delete BIT = 0;
IF EXISTS (SELECT *FROM inserted) SET @Insert = 1;
IF EXISTS (SELECT* FROM deleted( SET @Deleted = 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()
, GETDATE();
Another use case is 'Notifying users': which means we can send notification to different users using triggers most of the notifications will be about events happening in the database In this query, the Sales department must be notified when new orders are placed The stored procedure will be executed when an INSERT query happens
CREATE TRIGGER NewOrderNotification
ON Orders
AFTER INSERT
AS
EXECUTE SendNotifications @RecipientEmail = 'sales@freshfruit.com',
@EmailSubject = 'New Order Placed'
@EmailBody = 'A new Order was just placed'
time to practice
Copy Customer changes to History table
CREATE TRIGGER CopyCustomersToHistory
ON Customers
AFTER INSERT, UPDATE
AS
INSERT INTO CustomersHistory (CustomerID, Customer, ContractID, Address, PhoneNo, Email, ChangeDate)
SELECT CustomerID, Customer, ContractID, ContractDate, Address, PhoneNo, Email, GETDATE()
FROM inserted
Keep track of any modifications made to the contents of Orders
-- Add a trigger that tracks table changes
create trigger OrdersAudit
ON Orders
after INSERT, delete, update
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;
Uses cases for INSTEAD OF Trigger (DML)
Preventing certain operation from happening
Enforcing data integrity
Control the database statements
An example of trigger that prevent and notify admin
Let's say we don't want the regular users to make certain opertation on the database tables [like updating or deleting] and when they make so, we send the admin a notification
CREATE TRIGGER PreventCustomerRemoval
ON Customers
INSTEAD OF DELETE
AS
DECLARE @EmailBodyText NAVCHAR(50) = (SELECT 'User "' + ORIGINAL_LOGIN() + ' " tried to remove a customer from the database. ');
RAISEERROR('Customer entries are no subject to removal.', 16, 1);
EXECUTE SendNotification @RecipentEmail = 'admin@freshfruit.com'
, @EmailSubject = 'Suspicous database behaviour
, @EmailBody = @EmailBodyText;
Triggers with conditional logic
Triggers are not just limited to the prevention of operations, we can also use it to decide whether or not some of operations should succeed

here we prevent inserting any new orders when there is not sufficient stock of the product
CREATE TRIGGER ConfirmStock
ON Orders
INSTEAD OF INSERT
AS
IF EXISTS(SELET * FROM Product AS p
INNER JOIN inserted AS i ON i.Product = p.Product
WHERE p.Quantity < i.Quantity)
RAISERROR('You cannot place orders when there is no product stock', 16, 1);
ELSE
INSERT INTO dbo.Orders(Customer, Product, Quantity, OrderDate, TotalAmount)
SELECT Customer, Product, Quantity, OrderDate, TotalAmount FROM Inserted;
time for practice
-- 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;
Use cases for DDL Triggers
As you know DDL Triggers can be created at different levels (Database level ,Server level) [1] Database Level A trigger created at the database level can respond to statement related to tables, view interactions, and index management, as well as more specific statements to do with premissions management or statistics
CREATE_TABLE, ALTER_TABLE, DROP_TABLE
CREATE_VIEW, ALTER_VIEW, DROP_VIEW
CREATE_INDEX, ALTER_INDEX, DROP_INDEx
[2] Server Level
at the server level, the trigger used can respond to database management and controlling server permissions and the use of credentials
CREATE_DATEBASE, ALTER_DATABASe, DROP_DATEBASE
GRANT_SERVER, DENY_SERVER, REVOKE_SERVER
CREATE_CREDENTIAL, ALTER_CREDENTIAL, DROP_CREDENTIAL
check the online documentaion for the full list of DDL
Databasse auditing
we can keep track of the changes at the database level
CREATE TRIGGER DatabaseAudit
ON DATABASE
FOR DDL_TABLE_VIEW_EVENTS
AS
INSERT INTO [DatabaseAudit] ([EventType], [Database], [Object], [UserAccount]. [Query], [EventTime])
SELECT
EVENTDATA().value('/EVENT_INSTANCE/EventType)[1]', 'NAVCHAR(50)
)
Table of contents
Trigger Management and Optimization
Trigger Modifications
Because triggers are objects we can deal with them as we deal with any DB object by deleting or creating and so on.
Deleting A trigger on a table or view
Disabling a trigger
Deleting triggers
Deleting table and view triggers
DROP TRIGGER PreventNewDiscounts;
Deleting database level triggers
DROP TRIGGER PreventViewsModifications
ON DATABASE;
Deleting server triggers
DROP TRIGGER DisallowLinkedServers
ON ALL SERVER;
Disabling triggers
There are some cases when you need just to stop trigger for specific period of time AKA **Disabling" a trigger.
A deleted trigger can never be used again, unless you recreate the trigger.
📘 note
When you need to disable a trigger, you need to explicitly specify the object the trigger is attached to, even if it is a normal table.
DISABLE TRIGGER PreventNewDiscounts
ON Discount
DISABLE TRIGGER PreventViewsModifications
ON DATABASE
DISABLE TRIGGER DisallowLinkedServers
ON ALL SERVER
Re-enabling triggers
ENABLE TRIGGER PreventViewsModifications
ON DATABASE
Altering triggers
there are two main approaches for changing triggers after they were created
Create and Drop workflow
using
ALTER
In the first approach, you're going to create the trigger and if something happened and you wish to change it, you DROP
the trigger and re-create it. which is something frustrating during development, instead you can ALTER
the trigger in time
ALTER TRIGGER X
ON Y
INSTEAD OF DELETE
AS
-- Your changes
Trigger Management
To get information about the current triggers you've on your server, we'll explore the sys.triggers table which contains information about the system triggers
SELECT * FROM sys.triggers
this table contains about 13 attributes, but we are going to explore the most important ones.
name
trigger name
object_id
unique identifier of the trigger
parent_class
trigger type - 1 for table trigger - 0 for database trigger
parent_class_desc
textual describe of trigger type
parent_id
unique identifier of the parent object that trigger is attached to
create_date
date of creation
modify_date
date of last modifications
is_disabled
current state
is_instead_of_trigger
INSTEAD OF
or AFTER
trigger
If you want to know the sever level triggers
SELECT * FROM sys.server_triggers
the table will have the same structure as the databsase triggers level with the same information
What if you need to identify the events that will fire a trigger ?
this information is stored in sys.trigger_events
you don't need to memorize all of the events that will fire the triggers, they are contained in sys.trigger_event_types
the problem here is that the information is divided into many tables, if you want to form a good answer
"list the trigger along with their firing events and object they're attatched to" we need to join the tables together
SELECT t.name as TriggerName,
t.parent_class_desc AS TriggerType,
te.type_desc AS EventName,
o.name As AttatchedTo,
o.type_desc AS ObjectType
FROM sys.triggers AS t
INNER JOIN sys.trigger_events AS te
ON te.object_id = t.object_id
LEFT OUTER JOIN sys.objects AS o ON o.object_id = t.parent_id;
note: the second join is chosedn to be a LEFT
join beause database-level triggers do not appear as attached to an object
In real-world you'll not use those views in isolation, they usually combined together to get a useful information
Practice Time
-- 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';
-- counting AFTER triggers
SELECT COUNT(object_id) FROM
sys.triggers
WHERE is_instead_of_trigger = 'false'
troubleshooting Triggers
Keep a history of triggers runs
how to search for triggeers causing issues
Tracking Trigger Exectuins (system views)
one important thing to keep on mind when troubleshooting triggers is to have a history of their execution
note: SQL Server provides information on the execution of the triggers that are currently stored in memory, so when the triggers are removed from memory they areremoved from the view as well sys.dm_exec_trigger_stats
so how to get around this problem? by creating our custom solution
ALTER TRIGGER PreventOrdersUpdate
ON Orders
INSTEAD OF UPDATE
AS
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)
UPDATE Orders
SET Quanity = 400
WHERE ID = 600
This will raise an error, but also we got a **permenant record that we can use to track the history of triggers runs
SELECT * FROM TriggerAudit
How can we identify the triggers on a certain table or view? using sys.objects
table which contains information about the objects on the database.
SELECT name AS TableName,
Object_id AS TableID
FROM sys.objects
WHERE name = 'Products';
Products
123
Then
SELECT o.name
AS TableName, o.object_id
AS TableID, t.name
AS TriggerName, t.object_id
AS TriggerID, t.is_disabled
AS IsDisabled, t.is_instead_of_trigger AS IsInsteadOf
FROM sys.objects AS o
INNER JOIN sys.triggers AS t ON
t.parent_id = o.object_id
WHERE o.name ='Products'
TableName | TableID | TriggerName | TriggerID | IsDisabled | IsInsteadOf | | Products | 917578307 | TrackRetiredProducts | 1349579846 | 0 | 0 | | Products | 917578307 | ProductsNewItems | 1397580017 | 0 | 0 | | Products | 917578307 | PreventProductChanges | 1541580530 | 0 | 1 |
To identify the events capable of firing a trigger, we'lljoin to the sys.trigger_events
also
SELECT o.name
AS TableName, o.object_id
AS TableID, t.name
AS TriggerName, t.object_id
AS TriggerID, t.is_disabled
AS IsDisabled, t.is_instead_of_trigger AS IsInsteadOf
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 t.object_id = te.object_id
WHERE o.name ='Products'
| TableName | TableID | TriggerName | TriggerID | IsDisabled | IsInsteadOf | FiringEvent | | Products | 917578307 | TrackRetiredProducts | 1349579846 | 0 | 0 | DELETE | | Products | 917578307 | ProductsNewItems | 1397580017 | 0 | 0 | INSERT | | Products | 917578307 | PreventProductChanges | 1541580530 | 0 | 1 | UPDATE |
if we want to further view also the trigger definiton, we'll use OBJECT_DEFINITION()
method which return the definintion for an object Id passed as an argument
SELECT o.name
AS TableName, o.object_id
AS TableID, t.name
AS TriggerName, t.object_id
AS TriggerID, t.is_disabled
AS IsDisabled, t.is_instead_of_trigger AS IsInsteadOf,
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 t.object_id = te.object_id
WHERE o.name ='Products'
| TableName | TableID | TriggerName |...| FiringEvent | TriggerDefinition | | Products | 917578307 | TrackRetiredProducts |...| DELETE | CREATE TRIGGER TrackRetiredProducts ON Produc... | | Products | 917578307 | ProductsNewItems |...| INSERT | CREATE TRIGGER ProductsNewItems ON Products A... | | Products | 917578307 | PreventProductChanges |...| UPDATE | CREATE TRIGGER PreventProductChanges ON Produ... |
now you can inspect and modify the trigger definintion if needed
Practice Time
-- 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
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';
Use Case For DDL Triggers
where the trigger is created will influence the types of statements able to fire it
Database Auditing
Preventing server changes
CREATE TABLE PreventDatabaseDelete
ON ALL SERVER
FOR DROP_DATABASE
AS
PRINT "You are not allowed to remove existing databases."
ROLLBACK;
Resources
Last updated