There are several ways in which we can choose to implement our business logic. For example, we could use:
stored procedures
constraints
triggers
unique filtered indexes.
Over the coming sections we'll discuss the sort of situations where each approach may, or may not be appropriate
Use constraints where possible
In many cases, constraints are the easiest and simplest to use. To demonstrate this point, consider the Teams table shown in Listing 1, with a primary key constraint on the TeamID column.
Since we wish to forbid access to the base tables,
teams will be inserted into the table, one at a time, by calling a
stored procedure. Our business rule is simple: team names must be
unique. So, we need to decide where to implement this business rule. One
choice is to enforce it in the stored procedure, as shown in Listing 2.
So, we have a stored procedure that enforces our
rule, at least in the absence of high concurrency. However, what happens
when we need another stored procedure that modifies a single row in the
Teams table, or one that merges a batch of new rows into that
table? We'll need to re-implement this same logic for every stored
procedure that modifies this table. This is a form of copy-and-paste and
is both time consuming and error prone.
Besides, unless you can guarantee that no applications can run modifications directly against the Teams table, it's likely that your business rule will be bypassed at some point, and inconsistent data will be introduced.
It is much easier and safer to just create the business rule once, in one place, as a UNIQUE constraint, as shown in Listing 3.
We can now let the database engine make sure that
this business rule is always enforced, regardless of the module or
command that modifies the table.
Turn to triggers when constraints are not practical
As we have seen, constraints are extremely useful in
many simple cases. However, our business rules are often more complex,
and it is sometimes not possible or not practical to use constraints. To
demonstrate this point, let's add one more table, TeamMembers, which references the Teams table through the TeamID column, as shown in Listing 4.
Suppose that we need to implement the following
business rule: no team can have more than two members. Implementing this
business rule in a trigger is quite straightforward, as shown in Listing 5, and you only have to do it once. It is possible, but much more complex, to implement this rule via constraints.
With our business rule implemented in only one place,
we can comprehensively test just one object. In order to test this
trigger, we need some test data in our parent table, as shown in Listing 6.
The script shown next, in Listing 7,
verifies that we can successfully add new team members, as long as the
teams' sizes do not exceed the limit imposed by our trigger.
The script shown next, in Listing 8, verifies that we can successfully transfer team members between teams, as long as the teams' sizes do not exceed the limit.
So, we've proved that our trigger allows
modifications that do not violate our business rules. Now we need to
make sure that it does not allow modifications that do violate our
business rules; there are quite a few cases, and we need to verify them
all. First of all, Listing 9
verifies that we cannot add new team members if the resulting teams'
sizes are too big. All the statements in the script must, and do, fail.
Also, we need to make sure that we cannot transfer team members if the resulting teams' sizes are too big, as shown in Listing 10. Again, all the following statements fail as expected.
The amount of testing needed to ensure that a trigger
works as expected can be quite substantial. However, this is the
easiest alternative; if we were to re-implement this business rule in
several stored procedures, then the same amount of testing required for
the single trigger would be required for each of these procedures, in
order to ensure that every one of them implements our business rule
correctly.
Unique filtered indexes (SQL Server 2008 only)
Last, but not least, in some cases filtered indexes
also allow us to implement business rules. For example, suppose that we
need to make sure that each team has at most one team lead. If you are
using SQL Server 2008 and upwards, then a filtered index can easily
implement this business rule, as shown in Listing 11. I encourage you to try out this index and see for yourself that it works.