☰ Chapters
Week 6 · Triggers
CT004-3.5-3 Advanced Database Systems · Week 6

Integrity Constraints & Triggers

The five integrity constraints, referential actions, and DML triggers — AFTER vs INSTEAD OF, inserted/deleted tables.

Contents
    Topic & structure

    Learning outcome: implement triggers.

    Integrity enhancement features

    SQL lets us define five types of integrity constraint:

    #ConstraintEnforced by
    1Required dataNOT NULL
    2Domain constraintsCHECK (SQL Server has no CREATE DOMAIN)
    3Entity integrityPRIMARY KEY, UNIQUE
    4Referential integrityFOREIGN KEY … REFERENCES + ON UPDATE / ON DELETE
    5Enterprise constraints (business rules)Triggers (and CHECK where possible)

    1. Required data

    Some columns must always contain a valid value — e.g. every member of staff must have a job position (Manager, Assistant, …).

    ALTER TABLE Staff
    ALTER COLUMN position VARCHAR(50) NOT NULL;

    2. Domain constraints

    Every column has a domain (set of legal values). E.g. gender is a single character, either ‘M’ or ‘F’.

    ALTER TABLE Staff WITH NOCHECK
    ADD CONSTRAINT CK1 CHECK (gender = 'M' OR gender = 'F');
    Extra · WITH NOCHECK

    WITH NOCHECK means existing rows are not validated when the constraint is added — only future inserts/updates are checked. Without it, adding the constraint fails if any existing row violates it.

    3. Entity integrity

    The primary key of a table must contain a unique, non-null value for each row.

    PRIMARY KEY (staffNo)
    PRIMARY KEY (clientNo, propertyNo)     -- composite key
    UNIQUE (telNo)                         -- alternate key

    You can only have one PRIMARY KEY clause per table; uniqueness of alternate keys is ensured with UNIQUE.

    4. Referential integrity

    A foreign key (FK) is a column (or set of columns) that links each row in the child table to the row of the parent table containing the matching primary key.

    Definition

    Referential integrity: if a FK contains a value, that value must refer to an existing row in the parent table.

    FOREIGN KEY (branchNo) REFERENCES Branch (branchNo)
    ActionEffect of deleting a parent row
    CASCADEDelete the parent row and the matching child rows, and so on in a cascading manner.
    SET NULLDelete the parent row and set the child FK column(s) to NULL.
    SET DEFAULTDelete the parent row and set each FK column in the child to its default. Only valid if a DEFAULT is specified for the FK columns.
    NO ACTIONReject the delete from the parent. This is the default.
    Slide correction

    The slide says SET NULL is “only valid if FK columns are NOT NULL”. It is the opposite: SET NULL is only valid if the FK columns do not have the NOT NULL qualifier (they must be allowed to hold NULL).

    FOREIGN KEY (staffNo) REFERENCES Staff ON DELETE SET NULL
    FOREIGN KEY (ownerNo) REFERENCES PrivateOwner ON UPDATE CASCADE

    Reading: if a staff member is deleted, their properties become unassigned (staffNo = NULL). If an owner’s number changes, it is automatically changed in all their properties.

    5. Enterprise constraints (“business rules”)

    Additional rules specified by users or the organization. They can be applied using triggers. DreamHome examples:

    What are triggers?

    EventFires when…Typical use
    INSERTan INSERT runs on the tableWhen an order is inserted into Orders, reduce the on-hand inventory in Products (order 5 widgets → stock −5).
    UPDATEan UPDATE runs on the tableRecord in an Auditing table who changed the Salary column in Employees, and when.
    DELETEa DELETE runs on the tableNever physically delete a customer: roll back the delete and set Status = 'Inactive' instead.

    AFTER vs INSTEAD OF triggers

    AFTER (default)

    • Fires after the data modification: the change happens, then the trigger runs.
    • An AFTER trigger that rolls back is expensive — the data is changed twice when it shouldn’t have changed at all.
    • FOR is a synonym for AFTER.
    • Can be defined on tables only, not views.

    INSTEAD OF

    • Fires before/in place of the modification: the original statement is prevented and the trigger runs instead.
    • Commonly created on views to make them updatable — e.g. a view built with UNION can’t know which table to update; the trigger decides and updates the underlying table.
    • Only one INSTEAD OF trigger per action per table/view.

    The inserted and deleted tables

    Triggers use two special tables that are only accessible inside triggers. Their data is available immediately after the INSERT, UPDATE or DELETE.

    Statementinserted holdsdeleted holds
    INSERTthe new rows(empty)
    DELETE(empty)the removed rows
    UPDATEthe rows after the change (after image)the rows before the change (before image)
    UPDATE Table inserted deleted new recordsold records (before)

    When an UPDATE executes, the database engine effectively deletes the existing row (holding it in deleted) and adds a new row (holding it in inserted). The new row has the same data except for the modified columns.

    When are DML triggers used?

    Some facts about triggers

    Syntax for creating triggers

    CREATE TRIGGER [ schema_name. ] trigger_name
    ON { table | view }
    { FOR | AFTER | INSTEAD OF }
    { [ INSERT ] [ , ] [ UPDATE ] [ , ] [ DELETE ] }
    AS
    { sql_statement [ ; ] [ ...n ] }

    How an INSERT trigger works

    1. INSERT statement executed
    2. INSERT statement logged
    3. AFTER INSERT trigger statements executed
    CREATE TRIGGER [insrtWorkOrder] ON [Production].[WorkOrder]
    AFTER INSERT AS
    BEGIN
        SET NOCOUNT ON;
        INSERT INTO [Production].[TransactionHistory]
            ([ProductID], [ReferenceOrderID], [TransactionType],
             [TransactionDate], [Quantity], [ActualCost])
        SELECT inserted.[ProductID], inserted.[WorkOrderID],
               'W', GETDATE(), inserted.[OrderQty], 0
        FROM inserted;
    END

    Every new work order automatically writes a matching row to the transaction history.

    Example: rejecting an insert (business rule)

    CREATE TRIGGER LowCredit
    ON Purchasing.PurchaseOrderHeader
    AFTER INSERT
    AS
        DECLARE @creditrating int
    
        SELECT @creditrating = v.CreditRating
        FROM   Purchasing.PurchaseOrderHeader p
               INNER JOIN inserted i ON p.PurchaseOrderID = i.PurchaseOrderID
               JOIN Purchasing.Vendor v ON v.VendorID = i.VendorID
    
        IF @creditrating = 5
        BEGIN
            RAISERROR ('This vendor''s credit rating is too low to accept new purchase orders.', 16, 1)
            ROLLBACK TRANSACTION
        END

    Checks the vendor’s credit rating whenever a new purchase order is inserted; if it is poor (5), raises an error and rolls back the insert.

    How a DELETE trigger works

    1. DELETE statement executed
    2. DELETE statement logged
    3. AFTER DELETE trigger statements executed
    CREATE TRIGGER [delCategory] ON [Categories]
    AFTER DELETE AS
    BEGIN
        UPDATE P SET [Discontinued] = 1
        FROM [Products] P
             INNER JOIN deleted AS d ON P.[CategoryID] = d.[CategoryID]
    END;

    When a category is deleted, all its products are marked as discontinued.

    How an UPDATE trigger works

    1. UPDATE statement executed
    2. UPDATE statement logged
    3. AFTER UPDATE trigger statements executed
    CREATE TRIGGER [updtProductReview] ON [Production].[ProductReview]
    AFTER UPDATE NOT FOR REPLICATION AS
    BEGIN
        UPDATE [Production].[ProductReview]
        SET    [Production].[ProductReview].[ModifiedDate] = GETDATE()
        FROM   inserted
        WHERE  inserted.[ProductReviewID] = [Production].[ProductReview].[ProductReviewID];
    END;

    Automatically stamps ModifiedDate whenever a review is updated. NOT FOR REPLICATION stops the trigger firing when a replication agent makes the change.

    How an INSTEAD OF trigger works

    1. UPDATE, INSERT or DELETE statement executed
    2. Executed statement does not occur
    3. INSTEAD OF trigger statements executed
    CREATE TRIGGER [delEmployee] ON [HumanResources].[Employee]
    INSTEAD OF DELETE NOT FOR REPLICATION AS
    BEGIN
        SET NOCOUNT ON;
        DECLARE @DeleteCount int;
        SELECT @DeleteCount = COUNT(*) FROM deleted;
        IF @DeleteCount > 0
        BEGIN
            -- e.g. raise an error: employees cannot be deleted, only marked inactive
            ...
        END;
    END;

    Altering and dropping triggers

    ALTER TRIGGER [ schema_name. ] trigger_name
    ON { table | view }
    { FOR | AFTER | INSTEAD OF }
    { [ INSERT ] [ , ] [ UPDATE ] [ , ] [ DELETE ] }
    AS
    { sql_statement [ ; ] [ ...n ] }
    
    DROP TRIGGER [ schema_name. ] trigger_name

    Worked example: DreamHome business rules

    Extra · Manager salary ≤ 30,000
    CREATE TRIGGER trgManagerSalary ON Staff
    AFTER INSERT, UPDATE
    AS
    BEGIN
        IF EXISTS (SELECT * FROM inserted
                   WHERE position = 'Manager' AND salary > 30000)
        BEGIN
            RAISERROR('A manager''s salary must not exceed 30000.', 16, 1)
            ROLLBACK TRANSACTION
        END
    END

    (This one could also be a table-level CHECK, since it only uses Staff’s own columns.)

    Extra · Staff may not manage more than 100 properties
    CREATE TRIGGER trgMaxProperties ON PropertyForRent
    AFTER INSERT, UPDATE
    AS
    BEGIN
        IF EXISTS (SELECT p.staffNo
                   FROM   PropertyForRent p
                   WHERE  p.staffNo IN (SELECT staffNo FROM inserted)
                   GROUP BY p.staffNo
                   HAVING COUNT(*) > 100)
        BEGIN
            RAISERROR('Staff member already manages 100 properties.', 16, 1)
            ROLLBACK TRANSACTION
        END
    END

    This must be a trigger: it counts rows across the table, which a CHECK constraint cannot do. Note it handles multi-row inserts by using IN (SELECT … FROM inserted).

    Quick review

    Name the five integrity constraints.
    Required data, domain constraints, entity integrity, referential integrity, enterprise constraints.
    Which referential action is the default?
    NO ACTION — the delete/update of the parent is rejected.
    What’s in the inserted and deleted tables during an UPDATE?
    deleted = before image (old rows); inserted = after image (new rows).
    Why is an AFTER trigger that rolls back considered expensive?
    The data is modified first and then undone — changed twice when it shouldn’t have changed at all. An INSTEAD OF trigger can block the change up front.
    Why use a trigger instead of a CHECK constraint?
    CHECK can only reference columns in its own table/row. Rules involving other tables or aggregates (e.g. max 100 properties per staff) need triggers.