When enforcing "business rules" with triggers in SQL Server 2022, several important considerations must be addressed to ensure performance, maintainability, and data integrity:
✅ Key Considerations:
-
Complexity and Readability
- Triggers can encapsulate complex logic, but overuse or poor design can make them difficult to debug or maintain.
- Use clear naming conventions and document the purpose of each trigger.
-
Execution Order and Nested Triggers
-
Performance Impact
Area |
Recommendation |
Simplicity |
Keep logic minimal inside triggers. |
Maintenance |
Document all trigger logic clearly. |
Performance |
Avoid complex joins or cursors; use set-based operations. |
Debugging |
Test with all relevant DML operations (single-row and multi-row). |
Consistency |
Don't mix similar business rules in triggers and app code. |
Because triggers automatically perform an action after a change is made to your database, they are an important method for enforcing the business rules that govern your database. Business rules govern the conditions that must be met for a companys policies to be enforced.
Below is a sample Trigger implementation in SQL Server 2022 that demonstrates enforcing a business rule. The scenario is a common one: ensuring that an employee's salary increase does not exceed 20% in a single update, and logging any attempted violations for auditing purposes. This example incorporates the guidelines you provided (understanding rules, appropriate use of triggers, solid coding, and testability).
Scenario
- Business Rule: When updating an employee's salary in the Employees table, the new salary cannot exceed 120% of the original salary. If an attempt is made to violate this rule, the update is rolled back, and the attempt is logged in an audit table.
- Trigger Type: AFTER UPDATE (DML trigger).
- Features Demonstrated: Multi-row handling, error handling, logging, and clear coding practices.
Sample Code
-- Create the Employees table
CREATE TABLE Employees (
EmployeeID INT PRIMARY KEY IDENTITY(1,1),
FirstName NVARCHAR(50),
LastName NVARCHAR(50),
Salary DECIMAL(10, 2) NOT NULL CHECK (Salary > 0),
LastUpdated DATETIME DEFAULT GETDATE()
);
-- Create an Audit table to log salary update violations
CREATE TABLE SalaryAuditLog (
AuditID INT PRIMARY KEY IDENTITY(1,1),
EmployeeID INT,
OldSalary DECIMAL(10, 2),
NewSalary DECIMAL(10, 2),
AttemptedBy NVARCHAR(128),
AttemptDate DATETIME DEFAULT GETDATE(),
ErrorMessage NVARCHAR(500)
);
-- Create the Trigger
CREATE TRIGGER trg_EmployeeSalary_Update
ON Employees
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON; -- Prevent extra result sets from interfering
-- Declare variables for error handling
DECLARE @ErrorMessage NVARCHAR(500);
-- Check if Salary column was updated
IF UPDATE(Salary)
BEGIN
-- Use a TRY-CATCH block for robust error handling
BEGIN TRY
-- Check for salary increase > 20% in the updated rows
IF EXISTS (
SELECT 1
FROM inserted i
JOIN deleted d ON i.EmployeeID = d.EmployeeID
WHERE i.Salary > d.Salary * 1.20 -- New salary exceeds 120% of old salary
)
BEGIN
-- Log the violation to the audit table
INSERT INTO SalaryAuditLog (EmployeeID, OldSalary, NewSalary, AttemptedBy, ErrorMessage)
SELECT
i.EmployeeID,
d.Salary,
i.Salary,
SUSER_SNAME(), -- Capture the user attempting the update
'Salary increase exceeds 20% limit. Old Salary: ' + CAST(d.Salary AS NVARCHAR(20)) +
', New Salary: ' + CAST(i.Salary AS NVARCHAR(20))
FROM inserted i
JOIN deleted d ON i.EmployeeID = d.EmployeeID
WHERE i.Salary > d.Salary * 1.20;
-- Raise an error and roll back the transaction
SET @ErrorMessage = 'Update failed: Salary increase cannot exceed 20% of the original salary.';
THROW 50001, @ErrorMessage, 1;
END
ELSE
BEGIN
-- Update LastUpdated column for valid updates
UPDATE e
SET LastUpdated = GETDATE()
FROM Employees e
INNER JOIN inserted i ON e.EmployeeID = i.EmployeeID;
END
END TRY
BEGIN CATCH
-- Roll back the transaction if an error occurs
IF @@TRANCOUNT > 0
ROLLBACK;
-- Re-throw the error to the client
THROW;
END CATCH
END
END;
GO
You can test the trigger with the following SQL statements:
-- Insert sample data
INSERT INTO Employees (FirstName, LastName, Salary)
VALUES ('John', 'Doe', 50000.00),
('Jane', 'Smith', 60000.00);
-- Valid update: Increase salary by 10% (within limit)
UPDATE Employees
SET Salary = 55000.00 -- 50000 * 1.10
WHERE EmployeeID = 1;
-- Check the result
SELECT * FROM Employees; -- Salary updated, LastUpdated set
SELECT * FROM SalaryAuditLog; -- No audit entry
-- Invalid update: Increase salary by 30% (exceeds limit)
UPDATE Employees
SET Salary = 65000.00 -- 50000 * 1.30
WHERE EmployeeID = 1;
-- This will fail with an error and roll back
-- Check the audit log
SELECT * FROM SalaryAuditLog; -- Shows the violation attempt
SELECT * FROM Employees; -- Salary unchanged
Output for Invalid Update
The invalid update will produce an error like:
Msg 50001, Level 16, State 1, Line 1
Update failed: Salary increase cannot exceed 20% of the original salary.
The
SalaryAuditLog
table will contain a row with details of the failed attempt.
Why This Works for SQL Server 2022
- The syntax and features (e.g., inserted/deleted tables, THROW, SUSER_SNAME()) are fully supported in SQL Server 2022.
- The trigger leverages modern error-handling practices (TRY-CATCH) and is optimized for performance, aligning with SQL Server 2022’s capabilities.
- It is testable and maintainable, with clear logging for debugging or compliance.
Triggers affect the transactional state in which they are fired. Knowing these effects can prevent conflicts with constraints, locking, and blocking on the affected tables. Every transaction invokes various checks in the following order:
- IDENTITY INSERT check.
- Nullability constraint.
- Data-type check.
- INSTEAD OF trigger execution. If an INSTEAD OF trigger exists, then execution of the DML stops here. INSTEAD OF triggers are not recursive. (Recursive triggers are covered later .)
- Primary-key constraint.
- Check constraints.
- Foreign-key constraints.
- DML execution and update to the transaction log.
- AFTER trigger execution.
- Commit transaction. Based on SQL Server’s transaction flow, keep the following points in mind:
- An AFTER trigger occurs after all constraints are enforced. If a constraint is violated, then AFTER triggers are not fired.
- An "INSTEAD OF trigger" can circumvent foreign-key violations but not nullability, data-type, or identity-column violations.
- The "AFTER trigger" occurs before the DML transaction is committed, so it can roll back the transaction if the data is unacceptable.
- Trigger examples
Following are examples of business rules that are good candidates for enforcement with triggers:
- No employee can have a salary greater than $200,000 without the approval of a manager. The managers employee ID must be stored toprove that this salary was authorized.
- Whenever a sales transaction is greater than $50,000, the company president must be notified so that the salesperson can be personally congratulated.
- A sale cannot be made for an item that is NOT in inventory, unless the InventoryType field has a value of
B
.
All these rules can be enforced by using triggers. The rest of this module shows you how to do this.
In the next lesson, the creation of triggers will be discussed.