SQL-Server Triggers  «Prev  Next»

Lesson 5 Creating SQL Server Triggers
Objective Understand how to create a Trigger in SQL Server 2025

Creating Triggers 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.

Two ways to create a trigger

  1. Use SQL Server Management Studio (SSMS) to generate a starter script (helpful for discovery and navigation).
  2. Write the trigger in Transact-SQL (preferred for correctness, repeatability, and source control).

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.

Creating a trigger with SSMS

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:

  1. In Object Explorer, expand your database.
  2. Expand Tables, then locate the target table.
  3. Expand the table node (or right-click the table) and locate Triggers.
  4. Select New Trigger… (or a trigger template), then edit the generated T-SQL.
  5. Execute the script in a query window to create the trigger.
Microsoft SQL Server Management Studio (SSMS) 19.0.1
Microsoft SQL Server Management Studio (SSMS) 19.0.1

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.


Creating a new trigger
The screenshot shows a trigger “starter” definition. In modern SQL Server, you’ll typically edit this template into a statement-safe trigger that handles multi-row changes correctly.

Extracted Transact-SQL skeleton:

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 trigger body executes within the same transaction as the DML that fired it.


Write triggers as set-based, statement-safe code

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.

A complete example: enforce a salary cap rule

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.
  • Set-based checking against the inserted table.
  • THROW for clear error signaling (and a single place to “fail fast”).
  • No assumptions about single-row operations.

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.

Core trigger syntax you should recognize

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:

  • AFTER triggers for enforcing rules on base tables.
  • INSTEAD OF triggers primarily on views (to implement controlled write behavior).

You can create, view, and modify triggers using SSMS Object Explorer, shown below.


Object Explorer lists all triggers
Object Explorer lists triggers for a table and provides a context menu to script, alter, or drop them.

Options in older trigger templates

You may encounter older trigger templates containing options such as WITH ENCRYPTION, WITH APPEND, and NOT FOR REPLICATION. In modern systems:

  • WITH ENCRYPTION hides the trigger text, but it also harms maintainability and can complicate incident response. Most modern teams avoid it in favor of source control and proper permissions.
  • WITH APPEND appears in legacy examples; modern SQL Server supports multiple triggers per table without relying on append semantics.
  • NOT FOR REPLICATION can still matter in replication scenarios. Use it only when you understand the replication/CDC behavior you want.

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.


1. trigger_name is the name of the trigger you are going to create. A trigger is a database object, so it must have a unique name within the database

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.

2) table is the name of the table on which you will create the trigger
2) table is the table (or view) the trigger is attached to.

WITH ENCRYPTION encrypts the text so that it can not be read from the syscomments table.
3) WITH ENCRYPTION attempts to obscure the definition. Most modern deployments prefer source control and least-privilege access instead.

trigger_type is the type of trigger you wish to create. The type can be INSERT, UPDATE, or DELETE,  depending on the type of trigger you are creating.
4) trigger_type is one or more of: INSERT, UPDATE, DELETE.

5) WITH APPEND is an optional statement that indicates to SQL Server that if a trigger of the same type already exists, this trigger should be added
5) Legacy templates mention WITH APPEND. Modern SQL Server supports multiple triggers without relying on this pattern.

NOT FOR REPLICATION indicates that any replication operations that affect data in the table should not fir the trigger.
6) NOT FOR REPLICATION can prevent trigger execution during replication-driven changes, when that behavior is desired.

column is used if you wish to test for a specific column to have data inserted or updated into it. Multiple columns can be specified.
7) 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.

 Bitwise_operator, updated_bitmask, comparison_operator, and column_bitmask all are affected by the COLUMNS_UPDATED clause, which is 
used to test a bit-filed representing one or more columns that are updated.
8) 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.

9) trigger_code is the Transact-SQL code that you will use to implement your business rules, using the INSERTED and DELETED special trigger tables.
9) trigger_code is where you implement business rules. Use the inserted and deleted tables to write set-based, statement-safe logic.

Checklist: what “good” looks like for a new trigger

  • Schema-qualify everything (trigger name, table name, referenced objects).
  • Handle multi-row statements using set logic, not scalar variables.
  • Use inserted/deleted rather than querying the base table when validating the change set.
  • Keep triggers small: validate, log (if required), and fail fast.
  • Prefer THROW for raising errors and make rollback behavior explicit at the transaction level.
  • Test with real patterns: single-row, multi-row, bulk loads, and concurrency.
  • Document the business rule in plain language at the top of the trigger source.

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.


SEMrush Software 5 SEMrush Banner 5