| Lesson 5 | Creating SQL Server Triggers |
| Objective | Understand how to create a Trigger in SQL Server 2025 |
A trigger is a database object that automatically executes when data changes in a table (or view). In SQL Server, triggers are most commonly used to enforce business rules that cannot be expressed cleanly as a simple constraint, and to ensure the rule is enforced consistently for every caller (applications, ETL jobs, ad-hoc scripts, and administrative tools).
In SQL Server 2025, triggers are still created and managed using standard DDL:
CREATE, ALTER, and DROP. Although SSMS provides menu options and templates,
the “source of truth” for a trigger is always the Transact-SQL definition.
In production work, most teams treat triggers as code: they live in source control, are deployed via migration scripts, and are tested the same way as stored procedures and schema changes.
You can create a trigger using SSMS by generating a trigger script from the object explorer. The exact menu labels vary slightly across SSMS versions, but the workflow is consistent:
SSMS is excellent for navigation and for discovering what already exists, but triggers should still be reviewed, refactored, and committed like any other database code.
CREATE TRIGGER [TRIGGER NAME]
ON [employees]
FOR INSERT, UPDATE, DELETE
AS
-- trigger body goes here
What this means:
CREATE TRIGGER [TRIGGER NAME] defines the trigger object name (unique within the schema).ON [employees] attaches the trigger to a base table (or a view, in special cases).FOR INSERT, UPDATE, DELETE indicates which DML statements cause the trigger to fire.
The most important rule when creating triggers is this:
SQL Server triggers fire per statement, not per row.
If a single UPDATE statement modifies 500 rows, your trigger must correctly handle 500 rows at once.
SQL Server provides two special trigger tables that represent the change set:
inserted contains the new versions of rows (INSERT/UPDATE).deleted contains the old versions of rows (DELETE/UPDATE).
Because these are sets, business rules should typically be expressed using EXISTS,
set-based joins, and constraint-like checks, rather than row-by-row cursors.
The trigger below enforces a simple business rule: no employee may be inserted or updated with a salary greater than $250,000. This is an example of a rule that can’t be represented as a foreign key, and is often implemented as a trigger when you also want consistent enforcement across all write paths.
This implementation demonstrates several modern practices:
SET NOCOUNT ON to reduce noise and avoid surprises for callers.inserted table.THROW for clear error signaling (and a single place to “fail fast”).
CREATE OR ALTER TRIGGER dbo.trg_Employees_EnforceSalaryCap
ON dbo.Employees
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM inserted i
WHERE i.Salary > 250000
)
BEGIN
-- Fail fast: reject the statement and force a rollback
THROW 50001, 'Business rule violation: Salary must not exceed 250000.', 1;
END
END;
GO
Notice that this trigger is defined for AFTER INSERT, UPDATE only. If your business rule does not apply
to deletes, don’t include DELETE “for completeness” — unnecessary trigger firing adds overhead and increases
maintenance risk.
Triggers are created and modified using standard DDL. At a high level, the syntax looks like this:
CREATE TRIGGER schema.TriggerName
ON schema.TableName
AFTER | INSTEAD OF INSERT, UPDATE, DELETE
AS
BEGIN
-- trigger code
END;
In practice, you will commonly use:
You can create, view, and modify triggers using SSMS Object Explorer, shown below.
You may encounter older trigger templates containing options such as WITH ENCRYPTION,
WITH APPEND, and NOT FOR REPLICATION. In modern systems:
The series of images below illustrates a classic template-style trigger definition. The images are kept as-is, but the key lesson is this: templates are a starting point, not a finished trigger.
CREATE TRIGGER trigger_name
ON table
[WITH ENCRYPTION]
FOR trigger_type
AS
BEGIN
-- optional: IF UPDATE(column) checks
-- optional: COLUMNS_UPDATED() bitmask checks
-- trigger_code goes here
END
1) trigger_name is the trigger object name. Triggers are schema-scoped objects and must be unique within their schema.
table is the table (or view) the trigger is attached to.
WITH ENCRYPTION attempts to obscure the definition. Most modern deployments prefer source control and least-privilege access instead.
trigger_type is one or more of: INSERT, UPDATE, DELETE.
WITH APPEND. Modern SQL Server supports multiple triggers without relying on this pattern.
NOT FOR REPLICATION can prevent trigger execution during replication-driven changes, when that behavior is desired.
IF UPDATE(column) checks whether a column was referenced by an UPDATE statement. Use it carefully:
a column might be “updated” to the same value, and multi-row logic still applies.
COLUMNS_UPDATED() returns a bitmask of updated columns. In new development, many teams prefer clearer patterns
(explicit comparisons between inserted and deleted) unless bitmask checks are truly necessary.
trigger_code is where you implement business rules. Use the inserted and deleted
tables to write set-based, statement-safe logic.
In the next lesson, you will build triggers that satisfy unique business rules and learn how to test them safely using
the inserted and deleted trigger tables.