Oracle PL/SQL Triggers T-SQL · Triggers

T-SQL Triggers Introduction

Master T-SQL triggers — DML, DDL, and logon triggers. Comprehensive overview of trigger types, AFTER vs INSTEAD OF timing, the inserted/deleted pseudo-tables, the critical 'fires once per statement' rule, advantages and disadvantages, and when to use triggers vs alternatives like CHECK constraints.

A trigger is a special kind of stored procedure that the database engine runs automatically in response to specific events — without anyone calling it directly. Each trigger is bound to a particular table, view, database, or server, and fires when something happens to it (a row is inserted, a table is dropped, a user logs in).

You can think of a trigger as a piece of code that says "whenever X happens, also do this." The database guarantees the code runs every time, regardless of which application or user caused the event.

TypeFires onExamples of triggering events
DML triggers Data changes INSERT, UPDATE, DELETE on a table or view
DDL triggers Schema changes CREATE, ALTER, DROP of tables, indexes, procedures; GRANT/REVOKE
Logon triggers Connection events A user session is established (server-level only)

DML triggers respond to data changes on tables and views. They have access to two pseudo-tables — inserted and deleted — that hold the affected rows.

Operationinserted containsdeleted contains
INSERTThe new rows(empty)
DELETE(empty)The rows being removed
UPDATEThe new (post-update) rowsThe old (pre-update) rows

DML Trigger Timings

  • AFTER trigger (AFTER or its synonym FOR) — fires after the DML statement executes but before the surrounding transaction commits. Most common; used for auditing, cascading updates, and post-change validation.
  • INSTEAD OF trigger — fires instead of the DML statement, replacing it. Used to make complex views updatable, or to redirect an operation to a different table.

Common Use Cases

  • Audit logging (who changed what, when)
  • Cross-table validation that can't be expressed as a CHECK constraint
  • Maintaining derived columns or summary tables
  • Enforcing complex referential rules
  • Making views with joins updatable (INSTEAD OF triggers)

DDL triggers respond to schema-level events — they fire when someone runs CREATE TABLE, ALTER PROCEDURE, DROP INDEX, and so on. They live at one of two scopes:

ScopeCatches events fromCreated with
DatabaseThe current database onlyON DATABASE
ServerEvery database on the server instanceON ALL SERVER

Common Use Cases

  • Auditing schema changes — tracking who created or dropped objects and when
  • Enforcing change-control policies — block DROP TABLE outside a maintenance window
  • Notifying ops when schema changes happen in production

Logon triggers fire when a user session is established with the SQL Server instance. They live only at server scope (ON ALL SERVER) and let you inspect (or even reject) connections as they happen.

Common Use Cases

  • Logging connection attempts for security audit
  • Restricting logins from particular IPs or at particular times
  • Limiting the number of concurrent sessions per login
⚠️ Be careful with logon triggers. A bug or runaway query in a logon trigger can lock everyone out — including yourself. Always test in a non-production server first, and know how to start SQL Server in single-user mode for recovery.

The most common form — a DML trigger on a table:

CREATE [OR ALTER] TRIGGER schema.trigger_name ON table_or_view AFTER { INSERT, UPDATE, DELETE } [ NOT FOR REPLICATION ] AS BEGIN -- trigger body — can use 'inserted' and 'deleted' pseudo-tables END;

Key parts:

  • schema.trigger_name — the new trigger's name (use the schema explicitly).
  • ON table_or_view — what the trigger is bound to.
  • AFTER vs INSTEAD OF — when the trigger runs relative to the DML.
  • FOR is a synonym for AFTER in DML triggers — the two are interchangeable.
  • NOT FOR REPLICATION — if present, the trigger is skipped when the change comes from replication. Useful in replicated environments.
⚠️ Triggers fire ONCE PER STATEMENT, not once per row. If a single UPDATE changes 1,000 rows, the trigger fires once with 1,000 rows in inserted and deleted. The #1 bug in trigger code is assuming a single row — for example, writing SELECT @id = id FROM inserted, which silently picks one arbitrary row. Always think set-based: write trigger logic that works on any number of rows.

Quick example of the right and wrong way to write this:

T-SQL — Wrong (one-row assumption)
DECLARE @id INT, @name NVARCHAR(50);
SELECT  @id = id, @name = name FROM inserted;       -- ⚠️ silently grabs ONE row

INSERT INTO AuditLog (event, target_id, target_name)
VALUES ('inserted', @id, @name);
T-SQL — Right (set-based)
INSERT INTO AuditLog (event, target_id, target_name)
SELECT 'inserted', id, name
FROM   inserted;                                     -- ✓ handles ANY row count
  • Automatic enforcement. Rules apply to every change, no matter how it gets in — application code, ad-hoc query, bulk insert.
  • Centralized logic. Auditing or validation lives in one place, not scattered across multiple applications.
  • Cross-table integrity. Rules that span tables (more complex than what a CHECK or FK can express) can be enforced consistently.
  • Reduced application code. Logic that would otherwise need to be replicated in every client gets handled by the database.
  • Hidden cost. Triggers run on every qualifying DML — and they're invisible to the calling code. A "simple" UPDATE can become arbitrarily expensive when triggers do extra work behind the scenes.
  • Hard to debug. When a query fails or runs slowly, the trigger may be the cause but isn't visible in the user's SQL.
  • Order-of-execution surprises. Multiple triggers on the same event can fire in non-obvious order, especially after schema changes.
  • Lock and transaction interaction. Trigger code participates in the calling transaction — long-running trigger logic blocks every concurrent writer to the same table.
  • Easy to misuse. Triggers should not contain expensive joins, calls to external systems, or anything that takes more than milliseconds.
💡 Use triggers sparingly. They're powerful but often the wrong tool. Before writing one, ask: could a CHECK constraint, foreign key, computed column, or change-data-capture feature do the same job? If yes, prefer those — they're cheaper and more visible.

The pages that follow drill into each kind of trigger:

  • DDL Triggers — schema-event triggers using EVENTDATA()
  • DML Triggers — AFTER vs INSTEAD OF triggers, INSERTED/DELETED pseudo-tables, multi-event triggers
  • Enabling Triggers — turning a trigger back on after disabling it
  • Disabling Triggers — pausing trigger execution without dropping the trigger
  • Dropping Triggers — permanently removing triggers
  • A trigger is code the database runs automatically in response to events — DML (data), DDL (schema), or logon (connection).
  • DML triggers can be AFTER (fire after the DML, before commit) or INSTEAD OF (replace the DML entirely). FOR is a synonym for AFTER.
  • Inside a DML trigger, the inserted and deleted pseudo-tables hold the affected rows — empty for irrelevant operations.
  • Triggers fire once per statement, not once per row — write set-based logic that handles any row count.
  • Triggers add hidden cost to every DML; reach for constraints and other features first when they suffice.