| Lesson 4 | Introduction to business rules |
| Objective | Define what Business Rules are and why they are necessary |
SQL Server Business Rules
A business rule is a condition that reflects a specific real-world organizational need, translated into a rule the database itself enforces. Consider a rule like the one below, stated the way a human resources policy document might state it:
No employee shall have a salary of $100,000/yr or greater if they are at or below a paygrade of L-3.
Or consider a sales-side equivalent: an order cannot be shipped if the customer's credit limit is exceeded. Neither rule is a data type, and neither is a relationship between two tables in the usual sense, they're organizational policy. Business rules matter because the ability to accommodate them is critical to any software product's effectiveness: when a rule like this isn't enforced somewhere, a company's actual policies and its data silently drift apart.
Enforcing a business rule inside SQL Server itself, rather than relying solely on a client application to check it, guarantees that no matter how data enters the system, a hand-built API, a batch import, an ad-hoc query run directly against the database, the rule can't be bypassed. This also matters whenever more than one application writes to the same database: if five different client applications each calculate an order total themselves, they can each get it slightly wrong, or wrong in different ways. A rule enforced once, in the schema, is enforced the same way everywhere.
Ad Azure SQL
The Hierarchy of Business Rule Enforcement
Business rules can be quite complex, which is why there's no single way to enforce them. SQL Server 2025 enforces them across four layers, ordered from the simplest, lowest-overhead checks to the most complex procedural and event-driven logic:
| Layer | Scope | How It's Enforced |
| Declarative constraints | A single column, a single row, or a direct parent-child key relationship | CHECK, UNIQUE, FOREIGN KEY, and DEFAULT constraints, evaluated synchronously by the storage engine before a row is written |
| Computed columns and indexed views | Row-level derived formulas or cross-table aggregations | A formula defined once in the schema, calculated at query time or persisted on write, so every application sees the same result |
| Stored procedures and functions | Multi-table, multi-step transactional workflows | Explicitly invoked procedural logic, run inside a transaction |
| DML triggers | Cross-table, multi-row state validation and side effects | AFTER or INSTEAD OF triggers that fire automatically within the triggering transaction |
It's imperative that you understand and document the rules that govern how a company does business. This ensures there are no "holes" in the system that bypass a business rule. Business rules also enforce data integrity. The sections below take each enforcement layer in turn, along with what SQL Server 2025 adds to it.
Declarative Business Rules
Whenever a rule applies to a single row, or to a direct foreign key relationship, the simplest and fastest option is a declarative constraint, evaluated directly by the storage engine and visible to the Query Optimizer when it builds execution plans. The pay-grade rule mentioned earlier is a good example: if
PayGrade and
Salary are both columns on the same
Employees row, the rule can be enforced with nothing more than a table-level
CHECK constraint comparing the two columns directly, no trigger or procedure required.
SQL Server 2025 extends what a single constraint can express:
- Pattern and format rules. A
CHECK constraint can call the native REGEXP_LIKE function to validate a tax ID, a routing code, or a product serial number directly, without an external CLR function or a trigger.
- Cross-column row rules. A table-level
CHECK constraint can compare multiple columns in the same row at once, for example CHECK (ShipDate >= OrderDate AND DiscountPct BETWEEN 0.00 AND 0.40).
- Deterministic calculations. Rather than trusting several client applications to calculate a value like
TotalDue identically, a PERSISTED computed column enforces the exact formula once, inside the schema itself.
CREATE TABLE Sales.Invoices (
InvoiceID INT IDENTITY(1,1) CONSTRAINT PK_Invoices PRIMARY KEY,
CustomerID INT NOT NULL CONSTRAINT FK_Invoices_Customers
FOREIGN KEY REFERENCES Sales.Customers(CustomerID),
IssueDate DATE NOT NULL CONSTRAINT DF_Invoices_IssueDate DEFAULT (CAST(SYSDATETIME() AS DATE)),
DueDate DATE NOT NULL,
SubTotal DECIMAL(18,2) NOT NULL,
TaxRate DECIMAL(5,4) NOT NULL,
-- Business rule: deterministic calculation of TotalDue
TotalDue AS CONVERT(DECIMAL(18,2), SubTotal * (1 + TaxRate)) PERSISTED,
TaxExemptCert VARCHAR(20) NULL,
-- Business rule: DueDate must be on or after IssueDate
CONSTRAINT CK_Invoices_Dates CHECK (DueDate >= IssueDate),
-- Business rule: a 0% tax rate requires a valid exemption certificate format
CONSTRAINT CK_Invoices_TaxExemptRule CHECK (
TaxRate > 0.0000
OR (TaxRate = 0.0000 AND REGEXP_LIKE(TaxExemptCert, '^TX-[0-9]{6}$'))
)
);
Procedural Business Rules
A rule that spans multiple tables, aggregates historical rows, or needs conditional branching can't be expressed as a single constraint, and pushing a scalar function into a
CHECK constraint to simulate that logic tends to hurt bulk-write performance badly. SQL Server 2025 uses stored procedures and DML triggers for these cases instead.
A stored procedure acts as a transactional contract: using
TRY...CATCH and
THROW, it validates preconditions and modifies multiple tables atomically. Two SQL Server 2025 additions are especially useful here. Native fuzzy string-matching,
EDIT_DISTANCE_SIMILARITY and
JARO_WINKLER_SIMILARITY, lets a procedure block a near-duplicate vendor or customer record, a name or address that's almost, but not quite, identical to one already on file, before it's inserted. And
VECTOR_DISTANCE, computed over a native
VECTOR column, lets a procedure flag semantically similar records, two support tickets describing the same underlying issue in different words, say, something exact-match logic alone could never catch.
When a rule has to hold even when an ad-hoc
INSERT or
UPDATE bypasses the stored procedure entirely, an
AFTER or
INSTEAD OF trigger steps in, inspecting the
inserted and
deleted virtual tables. A critical design point: a trigger in SQL Server fires once per statement, not once per row, so a well-written trigger has to join against
inserted and
deleted using set-based logic, never a cursor or a scalar variable, so it behaves identically whether one row changed or ten thousand did.
CREATE TRIGGER Sales.TR_Orders_EnforceCreditLimit
ON Sales.Orders
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
-- Business rule: reject the batch if any affected customer's unpaid balance exceeds CreditLimit
IF EXISTS (
SELECT 1
FROM (SELECT DISTINCT CustomerID FROM inserted) AS i
INNER JOIN Sales.Customers AS c ON i.CustomerID = c.CustomerID
CROSS APPLY (
SELECT SUM(o.OrderTotal) AS TotalExposure
FROM Sales.Orders AS o
WHERE o.CustomerID = c.CustomerID
AND o.Status IN ('Pending', 'Approved')
) AS calc
WHERE calc.TotalExposure > c.CreditLimit
)
BEGIN
THROW 50001, 'Business rule violation: order exceeds the customer credit limit.', 1;
END;
END;
Security, Regulatory, and Event-Driven Business Rules
Some business rules govern who may see or change data, rather than what the data itself may contain:
- Row-level security. A rule like "regional managers may only view or modify orders in their own territory" is enforced transparently with a
CREATE SECURITY POLICY: a filter predicate restricts SELECT, UPDATE, and DELETE to the user's own rows, and a block predicate prevents an INSERT of a row outside their scope, both typically bound to SESSION_CONTEXT().
- Non-repudiation and audit rules. A regulatory rule requiring an unalterable audit trail of salary changes or financial transfers is enforced declaratively, using system-versioned temporal tables for time-travel state tracking and updatable ledger tables for cryptographic tamper-evidence, both covered in the previous lesson.
- Downstream event rules. Rather than a trigger calling an external service synchronously, holding an OLTP transaction open while it waits for a response, Change Event Streaming asynchronously streams committed transaction log events to Azure Event Hubs, letting downstream services react to a business-rule-relevant change without the database waiting on them.
Choosing Where to Enforce a Rule
Business rules can be complex enough that no single mechanism fits every case, which is why this lesson covers four of them rather than one, and each comes with real trade-offs. A constraint is the fastest option and the one the Query Optimizer trusts most, but it can only see the row in front of it, it can't look up a related row in another table or aggregate historical data. A stored procedure makes the rule explicit and easy to audit, since anyone reading the procedure's code sees the rule stated plainly, but it only runs when something actually calls it; a direct INSERT or UPDATE against the table bypasses it entirely. A trigger closes that gap, since it fires automatically no matter how the write arrived, but it adds overhead to every single write against that table, including the overwhelming majority that never come close to violating the rule.
As a general guide: reach for a declarative constraint whenever a rule touches only a single row or a direct foreign key relationship; reach for a stored procedure when a rule needs multi-table transactional logic invoked explicitly; and reach for a trigger only when a rule must hold even against ad-hoc statements that bypass your procedures entirely. Each of these mechanisms is discussed in more depth elsewhere in this course, and this lesson serves as the jumping-off point for thinking in terms of business rules generally.
In the next lesson, you will learn about database normalization.
