IT Learning HubIT Learning Hub
PRACTICE 05 · BTHT5

Triggers in SQL Server

5 periodsDatabaseSQL Server

Objective: Understand how SQL Server automatically runs a block of statements when data in a table undergoes an INSERT, UPDATE, or DELETE operation.

01 · WHAT IS A TRIGGER?

What Is a Trigger?

A Trigger is a piece of SQL code attached to a table or view, automatically fired by SQL Server when an INSERT, UPDATE, or DELETE operation occurs.

Without a Trigger vs. With a Trigger

User ↓ INSERT / UPDATE / DELETE ↓ Trigger fires ↓ Check / process / notify ↓ Data is updated

Example: "don't allow a product's quantity to go below 0." When a user runs UPDATE SANPHAM SET SOLUONG = -10 WHERE MASP = 'SP01';, the Trigger detects SOLUONG < 0 ➜ raises an error ➜ ROLLBACK ➜ cancels the operation.

Why Do We Need Triggers?

Triggers are typically used to automatically enforce rules right at the database layer:

  • Data validation: Product quantity ≥ 0
  • Preventing invalid operations: Don't allow deleting a customer who has transactions
  • Automatic updates: Adding an invoice line item ➜ automatically update the invoice total
  • Change tracking: Old customer name ➜ new name
  • Data synchronization: Invoice line changes ➜ update invoice total ➜ update customer's total spend

02 · TYPES OF TRIGGER

AFTER and INSTEAD OF

SQL Server supports two main types of Trigger. SQL Server does not support BEFORE TRIGGER the way some other database systems do.

AFTER - runs after the DML operation completes successfully (AFTER INSERT, AFTER UPDATE, AFTER DELETE). INSTEAD OF - replaces the original DML operation with the statements defined inside the Trigger.

TypeHow It WorksExample
AFTERThe original operation happens first, then the Trigger processesValidate and update data
INSTEAD OFThe Trigger replaces the original operationPrevent deletion
BEFORENot supported in SQL Server-
AFTER: DELETE ➜ Data is deleted ➜ Trigger ➜ Further processing INSTEAD OF: DELETE ➜ Trigger ➜ Original DELETE does NOT happen

03 · TRIGGER SYNTAX

CREATE TRIGGER Syntax

CREATE TRIGGER <Trigger_Name>
ON <Table_Name>
AFTER | INSTEAD OF INSERT, UPDATE, DELETE
AS
BEGIN
    -- Statements to run
END;

FOR can also be used in place of AFTER.

Breaking Down Each Part

CREATE TRIGGER trg_AfterInsert_KhachHang
ON KHACHHANG
AFTER INSERT
AS
BEGIN
    -- processing
END;

CREATE TRIGGER creates a new Trigger. The name should be descriptive, e.g. trg_AfterInsert_KhachHang, trg_CheckQuantity_SanPham, trg_PreventDelete_NhanVien. ON KHACHHANG attaches the Trigger to that table. AFTER INSERT runs after an INSERT occurs. BEGIN...END contains the statements to execute.

Example - AFTER INSERT

When a new customer is added, display a message:

CREATE TRIGGER trg_AfterInsert_KhachHang
ON KHACHHANG
AFTER INSERT
AS
BEGIN
    PRINT N'Khách hàng mới đã được thêm vào KHACHHANG.';
END;

Test it:

INSERT INTO KHACHHANG
VALUES (
    'KH99',
    N'Nguyễn Văn A',
    N'Hà Nội',
    '0909000000',
    '2026-08-11',
    0
);
The user does not need to call the Trigger manually - SQL Server automatically fires the Trigger right after the INSERT succeeds.

Example - INSTEAD OF DELETE

Prevent customers from being deleted:

CREATE TRIGGER trg_PreventDelete_KhachHang
ON KHACHHANG
INSTEAD OF DELETE
AS
BEGIN
    PRINT N'Không được xóa khách hàng.';
END;

When running DELETE FROM KHACHHANG WHERE MAKH = 'KH01';, instead of deleting, the system just prints "Customers cannot be deleted."

04 · ERROR CHECKING AND HANDLING

A Data-Validating Trigger

Problem: the quantity in SANPHAM must not be less than 0.

CREATE TRIGGER trg_CheckQuantity_SanPham
ON SANPHAM
AFTER INSERT, UPDATE
AS
BEGIN

    IF EXISTS (
        SELECT *
        FROM SANPHAM
        WHERE SOLUONG < 0
    )
    BEGIN
        RAISERROR (
            N'Số lượng không thể âm.',
            16,
            1
        );

        ROLLBACK;
        RETURN;
    END

END;

Breaking It Down

IF EXISTS ➜ RAISERROR ➜ ROLLBACK ➜ RETURN ➜ Cancel the invalid operation

IF EXISTS(...) checks whether a record violating the condition exists. RAISERROR(N'...', 16, 1) raises an error - severity level 16 is the level typically used for user-caused errors (bad input, rule violations). ROLLBACK cancels the current transaction, returning the data to its previous state.

Common mistake: calling RAISERROR and ROLLBACK but forgetting RETURN right after. If the Trigger has any statements after that point, they'll still run even though the transaction was already cancelled, potentially causing errors or unintended behavior. Get in the habit of always writing the full trio RAISERROR ➜ ROLLBACK ➜ RETURN, not stopping at just ROLLBACK.

PRINT vs. RAISERROR

PRINTRAISERROR
PurposeNotificationError reporting
Can stop the operationNoYes
Used with ROLLBACKPossible, but not automaticUsually paired together
Example"Customer added""Quantity cannot be negative"

QUICK CHECK

To fully cancel an UPDATE operation when a Trigger detects invalid data, what command is needed besides RAISERROR?

05 · INSERTED AND DELETED

INSERTED and DELETED

Inside a Trigger, SQL Server provides two virtual tables: INSERTED and DELETED. They only exist within the scope of the Trigger, and hold data related to the DML operation currently taking place.
OperationINSERTEDDELETED
INSERTThe new record-
DELETE-The deleted record
UPDATEThe new valueThe old value
Memory trick: INSERTED = the new one. DELETED = the old / removed one.

Declaring Local Variables (@)

A local variable in T-SQL always starts with @, declared with DECLARE - it only exists within the scope of the current batch/Trigger, and is used to hold a value pulled from INSERTED/DELETED before comparing it or processing it further.
DECLARE @VariableName DataType;

Declare several variables at once, separated by commas:

DECLARE @MaKH CHAR(4), @NgayDK SMALLDATETIME;

A newly declared variable starts out as NULL. There are 2 ways to assign a value:

MethodSyntaxWhen to Use
SETSET @MaKH = 'KH01';Assign a single known value, or a subquery guaranteed to return exactly 1 row.
SELECTSELECT @MaKH = MAKH FROM inserted;Assign directly from the result of a SELECT.
DECLARE @MaKH CHAR(4);

SELECT @MaKH = MAKH
FROM inserted;

PRINT N'Customer just added: ' + @MaKH;
The key difference between SET and SELECT: If the subquery/source table returns more than 1 row, SET @x = (SELECT ...) will raise an error immediately - but SELECT @x = column FROM table raises no error at all. It just silently assigns the value from the last row processed (the order isn't guaranteed) and drops every other row. This is exactly where the "Trigger only works for 1 row" bug covered in section 07 comes from - there's no error message to catch it, the code just runs "fine" while being silently wrong.

Try It Yourself

Click a button to see what INSERTED/DELETED contain for each operation (example: renaming a customer from "Nguyễn Văn A" to "Nguyễn Văn B"):

VISUAL DEMO

DELETED (old data)
(empty)
INSERTED (new data)
HOTEN = 'Nguyễn Văn A' (new record)

INSERT ➜ only INSERTED has data · DELETE ➜ only DELETED has data · UPDATE ➜ both have data.

CORRESPONDING STATEMENT
INSERT INTO KHACHHANG (MAKH, HOTEN)
VALUES ('KH01', N'Nguyễn Văn A');

Example - Reading INSERTED

CREATE TRIGGER trg_AfterInsert_KhachHang
ON KHACHHANG
AFTER INSERT
AS
BEGIN
    DECLARE @MAKH CHAR(4);

    SELECT @MAKH = MAKH
    FROM INSERTED;

    PRINT N'Khách hàng mới được thêm vào với MAKH: '
        + CAST(@MAKH AS VARCHAR);
END;

Example - Reading DELETED

CREATE TRIGGER trg_AfterDelete_KhachHang
ON KHACHHANG
AFTER DELETE
AS
BEGIN
    SELECT *
    FROM DELETED;
END;

Full Example - Tracking a Name Change (UPDATE)

On UPDATE, DELETED holds the old value and INSERTED holds the new value, so you can compare "old value ➜ new value":

CREATE TRIGGER trg_AfterUpdate_KhachHang
ON KHACHHANG
AFTER UPDATE
AS
BEGIN

    DECLARE
        @OldName VARCHAR(40),
        @NewName VARCHAR(40);

    SELECT @OldName = HOTEN
    FROM DELETED;

    SELECT @NewName = HOTEN
    FROM INSERTED;

    PRINT N'Tên khách hàng đã thay đổi từ "'
        + @OldName
        + N'" sang "'
        + @NewName
        + N'".';

END;

06 · REAL-WORLD USES

Real-World Applications of Triggers

Common mistake: using a Trigger for simple constraints that a CHECK CONSTRAINT or FOREIGN KEY already handles (e.g. just to block SOLUONG < 0, or to set a default value). A Trigger runs after the DML operation has already happened, adding extra processing cost - only use a Trigger when the logic goes beyond what a Constraint can do - for example needing to automatically update data in another table, or checking a condition based on data across multiple tables.

Checking Existence Before Insert

Don't allow adding an invoice if the customer doesn't exist:

INSERT HOADON ➜ Get MAKH from INSERTED ➜ Check KHACHHANG ➜ Exists? Yes ➜ Allow | No ➜ RAISERROR + ROLLBACK

IF EXISTS checks whether data matching a condition exists; IF NOT EXISTS checks the opposite - that no matching data exists.

A Trigger That Auto-Updates Data

Tables HOADON(MAHD, TRIGIA) and CTHD(MAHD, MASP, SOLUONG, DONGIA). When INSERT INTO CTHD happens, the Trigger recalculates TRIGIA and runs UPDATE HOADON.

A Trigger That Updates Customer Sales Totals

A more advanced problem: adding/editing an invoice ➜ the Trigger computes the total value ➜ updates DOANHSO in KHACHHANG.

Preventing Deletion of Referenced Data

Don't allow deleting an employee if they've already made sales:

DELETE NHANVIEN ➜ Check HOADON ➜ Has invoices? Yes ➜ ROLLBACK | No ➜ Allow

Automatically Setting the Registration Date

When adding a new customer, if NGDK isn't provided, the system automatically uses the current date via GETDATE().

Some Extended Problems

  • A product can only be added to an invoice if it exists and has enough stock
  • The total value in CTHD must match HOADON
  • Invoices over 1,000,000,000 must switch to "Special Review" status
  • The total quantity of products sold in a single day must not exceed 10,000
  • An employee cannot become their own manager

07 · TRIGGERS THAT HANDLE MULTIPLE ROWS

Triggers Should Think in Terms of Sets

Students often write:

DECLARE @MAKH CHAR(4);

SELECT @MAKH = MAKH
FROM INSERTED;

This is easy to understand when illustrating a single record, but a single INSERT INTO ... SELECT ... statement can insert many rows at once - and as section 05 already pointed out, SELECT @x = column FROM inserted raises no error at all with multiple rows, it just silently drops the rest.

INSERTED ├── row 1 ├── row 2 ├── row 3 ├── ... └── row n
A Trigger should be designed with set-based thinking instead of assuming there's always just one row - this is an important skill when moving from classroom exercises to real-world SQL. Don't just think "INSERTED ➜ 1 row ➜ 1 variable" - you need to handle the entire set of data in INSERTED/DELETED.

Local Variables vs. IF EXISTS/JOIN

These are 2 common ways to write the same data-validation problem in a Trigger:

Local variable (DECLARE + SELECT @x = ...)IF EXISTS/NOT EXISTS + JOIN
Correct with multiple rows?Only correct when the operation is guaranteed to affect exactly 1 rowAlways correct, whether 1 row or many
How it checksAssign a value into a variable, then compareCheck "does a violating row exist" across the whole inserted/deleted set
When to useSimple illustrative examples, or when it's 100% guaranteed to be 1 rowShould be the default for every data-validation Trigger
-- Local variable - only safe with exactly 1 row
DECLARE @SoLuong INT;
SELECT @SoLuong = SOLUONG FROM inserted;
IF (@SoLuong < 0)
BEGIN
    RAISERROR (N'Quantity cannot be negative.', 16, 1);
    ROLLBACK TRANSACTION;
    RETURN;
END
-- IF EXISTS - correct for any number of rows
IF EXISTS (SELECT * FROM inserted WHERE SOLUONG < 0)
BEGIN
    RAISERROR (N'Quantity cannot be negative.', 16, 1);
    ROLLBACK TRANSACTION;
    RETURN;
END

In the BTTH5 exercises, most questions should present IF EXISTS/NOT EXISTS as the main answer - the local-variable approach is still kept in the answer key as an "Alternative approach" for reference, with a clear note on its limitation: it's only correct when the operation affects exactly 1 row.

08 · BUILDING A TRIGGER, STEP BY STEP

The 6-Step Process

When facing a Trigger exercise, follow these 6 steps:

1. Identify the event: INSERT / UPDATE / DELETE ↓ 2. Identify the table: ON ... ↓ 3. Identify the type: AFTER or INSTEAD OF ↓ 4. Identify the data to check: INSERTED / DELETED / other tables ↓ 5. Write the condition: IF EXISTS / IF NOT EXISTS ↓ 6. Handle it: PRINT / RAISERROR / ROLLBACK / INSERT / UPDATE / DELETE

Managing Triggers

Dropping a Trigger:

DROP TRIGGER trg_AfterInsert_KhachHang;

Viewing Trigger information:

SELECT *
FROM INFORMATION_SCHEMA.TRIGGERS;

Week 5 Cheat Sheet

TRIGGER ➜ Runs automatically when DML happens INSERT/UPDATE/DELETE ➜ The operations that fire a Trigger AFTER ➜ Runs after the DML operation INSTEAD OF ➜ Replaces the DML operation INSERTED ➜ The new data DELETED ➜ The old / removed data DECLARE @x ➜ Declares a local variable SET @x = ... ➜ Assigns 1 value, errors if the subquery has >1 row SELECT @x = ...FROM... ➜ Assigns from a SELECT, SILENTLY keeps only 1 row IF EXISTS ➜ Checks that matching data exists IF NOT EXISTS ➜ Checks that no matching data exists PRINT ➜ A notification RAISERROR ➜ An error ROLLBACK ➜ Cancels the operation / transaction

The First Five Weeks of IT004

Week 1: creating a database. Week 2: querying. Week 3: advanced querying. Week 4: processing data (CAST/CASE/Subquery). Week 5: automation & control with Triggers, INSERTED/DELETED, RAISERROR/ROLLBACK. Per the course outline, Week 6 will be review and a practical exam - the timing and format of the exam will be announced later.

09 · PRACTICE EXERCISES

Comprehensive Practice

OFFICIAL ASSIGNMENT · BTTH5

Trigger Exercises

See the full assignment, attached files, and detailed answers (access code required) for BTTH5.

View exercise & answers ›
EXTRA PRACTICE · OPTIONAL

Extra Trigger problems to practice with - not part of the BTTH5 submission. Each has a hidden hint - click to reveal.

01

No Negative Quantities

Create a Trigger: don't allow SOLUONG < 0 in SANPHAM.

Hint
AFTER INSERT, UPDATE + IF EXISTS + RAISERROR + ROLLBACK
02

Change Tracking

When a customer's name changes, display: 'Customer name changed from "..." to "..."'.

Hint
INSERTED + DELETED + AFTER UPDATE
03

Prevent Deletion

Don't allow deleting an employee if they already have invoices.

Hint
INSTEAD OF DELETE + DELETED + IF EXISTS
04

Automatic Update

When an invoice line item is added, automatically update the invoice total.

Hint
AFTER INSERT + INSERTED + UPDATE
05★★

When an invoice is added or updated, automatically update the customer's sales total.

Hint
AFTER INSERT, UPDATE + INSERTED + UPDATE KHACHHANG.DOANHSO