Programming
 |
|
| You are reading part 4 from "Physical data storage in SQL Server 2005 and 2008," excerpted from A Developer's Guide to Data Modeling for SQL Server, Covering SQL Server 2005 and 2008, by Eric Johnson and Joshua Jones, copyright 2008, printed with permission from Addison-Wesley Professional. |
|
|
 |
 |
In addition to the objects that are used to store data and implement data
integrity, SQL Server provides several objects that allow you to write code
to manipulate your data. These objects can be used to insert, update,
delete, or read data stored in your database, or to implement business rules
and advanced data integrity. You can even build "applications" completely
contained in SQL Server. Typically, these applications are very small and
usually manipulate the data in some way to serve a function or for some
larger application.
Stored Procedures
Most commonly, when working with code in SQL Server you will work
with a stored procedure (SP). SPs are simply compiled and stored T-SQL
code. SPs are similar to views in that they are compiled and they generate
an execution plan when called the first time. The difference is that SPs, in
addition to selecting data, can execute any T-SQL code and can work with
parameters. SPs are very similar to modules in other programming languages.
You can call a procedure and allow it to perform its operation, or
you can pass parameters and get return parameters from the SP.
Like columns, parameters are configured to allow a specific data
type. All the same data types are used for parameters, and they limit the
kind of data you can pass to SPs. Parameters come in two types: input and
output. Input parameters provide data to the SP to use during their execution,
and output parameters return data to the calling process. In addition
to retrieving data, output parameters can be used to provide data to
SPs. You might do this when an SP is designed to take employee data and
update a record if the employee exists or insert a new record if the employee
does not exist. In this case, you might have an EmployeeID parameter
that maps to the employee primary key. This parameter would accept
the ID of the employee you intend to update as well as return the new employee
ID that is generated when you insert a new employee.
SPs also have a return value that can return an integer to the calling
process. Return values are often used to give the calling process information
about the success of the stored procedure. Return values differ
from output parameters in that return values do not have names and you
get only one per SP. Additionally, SPs always return an integer in the return
value, even if you don't specify that one be returned. By default, an
SP returns 0 (zero) unless you specify something else. For this reason, 0 is often used to designate success and nonzero values specify return error
conditions.
SPs have many uses; the most common is to manage the input and retrieval
of your data. Often SPs are mapped to the entities you are storing.
If you have student data in your database, you may well have SPs named
sp_add_student, sp_update_student, and sp_retrieve_student_data. These
SPs would have parameters allowing you to specify all the student data that
ultimately needs to be written to your tables.
Like views, SPs reduce your database's complexity for users and are
more efficient than simply running T-SQL repeatedly. Again, SPs remove
the need to update application code if you need to change your database.
As long as the SP accepts the same parameters and returns the same data
after you make changes, your application code does not have to change. In
Chapter 11 we talk in great detail about using stored procedures.
User-Defined Functions
Like any programming language, T-SQL offers functions in the form of
user-defined functions (UDFs). UDFs take input parameters, perform
an action, and return the results to the calling process. Sound similar to a
stored procedure? They are, but there are some important differences.
The first thing you will notice is a difference in the way UDFs are called.
Take a look at the following code for calling an SP.
DECLARE @num_in_stock int
EXEC sp_check_product_stock @sku = 4587353,
@stock_level = @num_in_stock OUTPUT
PRINT @num_in_stock
You will notice a few things here. First, you must declare a variable to store
the return of the stored procedure. If you want to use this value later, you
need to use the variable; that's pretty simple.
Now let's look at calling a UDF that returns the same information.
DECLARE @num_in_stock int
SET @num_in_stock = dbo.CheckProductStock (4587353)
PRINT @num_in_stock
The code looks similar, but the function is called more like a function call
in other programming languages. You are probably still asking yourself,
"What's the difference?" Well, in addition to calling a function and putting
its return into a variable, you can call UDFs inline with other code.
Consider the following example of a UDF that returns a new employee ID.
This function is being called inline with the insert statement for the employee
table. Calling UDFs in this way prevents you from writing extra
code to store a return variable for later use.
INSERT INTO employee (employeeid, firstname, lastname)
VALUES (dbo.GetNewEmployeeID(), 'Eric', 'Johnson')
The next big difference in UDFs is the type of data they return. UDFs
that can return single values are known as scalar functions. The data the
function returns can be defined as any data type except for text, ntext,
image, and timestamp. To this point, all the examples we have looked at
have been scalar values.
UDFs can also be defined as table-valued functions: functions that
return a table data type. Again, table-valued functions can be called inline
with other T-SQL code and can be treated just like tables. Using the following
code, we can pass the employee ID into the function and treat the
return as a table.
SELECT * FROM dbo.EmployeeData(8765448)
You can also use table-valued functions in joins with other functions or
with base tables. UDFs are used primarily by developers who write T-SQL
code against your database, but you can use UDFs to implement business
rules in your model. UDFs also can be used in check constraints or triggers
to help you maintain data integrity.
Triggers
Triggers and constraints are the two most common ways to enforce data integrity
and business rules in your physical database. Triggers are stored
T-SQL scripts, similar to stored procedures, that run when a DML statement
(other than SELECT) is issued against a table or view. There are two
types of DML triggers available in SQL Server.
With an AFTER trigger, which can exist only on tables, the DML
statement is processed, and after that operation completes, the trigger code is run. For example, if a process issues an insert to add a new employee
to a table, the insert triggers the trigger. The code in the trigger is
run after the insert as part of the same transaction that issued the insert.
Managing transactions is a bit beyond the scope of this book, but you
should know that because the trigger is run in the same context as the
DML statement, you can make changes to the affected data, up to and including
rolling back the statement. AFTER triggers are very useful for verifying
business rules and then canceling the modification if the business
rule is not met.
During the execution of an AFTER trigger, you have access to two virtual
tables—one called Inserted and one called Deleted. The Deleted
table holds a copy of the modified row or rows as they existed before a
delete or update statement. The Inserted table has the same data as the
base table has after an insert or update. This arrangement allows you to
modify data in the base table while still having a reference to the data as it
looked before and after the DML statement.
These special temporary tables are available only during the execution
of the trigger code and only by the trigger's process. When creating
AFTER triggers, you can have a single trigger fire on any combination of
insert, update, or delete. In other words, one trigger can be set up to run
on both insert and update, and a different trigger could be configured to
run on delete. Additionally, you can have multiple triggers fire on the same
statement; for example, two triggers can run on an update. If you have
multiple triggers for a single statement type, the ordering of such triggers
is limited. Using a system stored procedure, sp_settriggerorder, you can
specify which trigger fires first and which trigger fires last. Otherwise, they
are fired in the middle somewhere. In reality, this isn't a big problem. We
have seen very few tables that had more than two triggers for any given
DML statement.
INSTEAD OF triggers are a whole different animal. These triggers
perform in the way you would expect: The code in an INSTEAD OF trigger
fires in place of the DML statement that caused the trigger to fire. Unlike
AFTER triggers, INSTEAD OF triggers can be defined on views as well as
tables. Using them, you can overcome the limitation of views that have multiple
base tables. As mentioned earlier, you can update a view only if you
limit your update to affecting only a single base table. Using an INSTEAD
OF trigger, you can update all the columns of a view and use the trigger to
issue the appropriate update against the appropriate base table. You can also
use INSTEAD OF triggers to implement advanced data integrity or business
rules by completely changing the action of a DML statement.
You can also control trigger nesting and recursion behavior. With
nested triggers turned on, one trigger firing can perform a DML and cause
another trigger to fire. For example, inserting a row into TableA causes
TableA's insert trigger to fire. TableA's insert trigger in turn updates a
record in TableB, causing TableB's update trigger to fire. That is trigger
nesting—one trigger causing another to fire—and this is the default behavior.
With nested triggers turned on, SQL Server allows as many as 32
triggers to be nested. The INSTEAD OF trigger can nest regardless of the
setting of the nested triggers option.
Server trigger recursion specifies whether or not a trigger can perform
a DML statement that would cause the same trigger to fire again. For
example, an update trigger on TableA issues an additional update on
TableA. With recursive triggers turned on, it causes the same trigger to fire
again. This setting affects only direct recursion; that is, a trigger directly
causes itself to fire again. Even with recursion off, a trigger could cause another
trigger to fire, which in turn could cause the original trigger to fire
again. Be very careful when you use recursive triggers. They can run over
and over again, causing a performance hit to your server.
CLR Integration
As of SQL Server 2005, we gained the ability to integrate with the .NET
Framework Common Language Runtime (CLR). Simply put, CLR integration
allows you to use .NET programming languages within SQL Server
objects. You can create stored procedures, user-defined functions, triggers,
and CLR user-defined types using the more advanced languages available
in Microsoft .NET. This level of programming is beyond the scope of this
book, but you need to be aware of SQL Server's ability to use CLR. You
will likely run into developers who want to use CLR, or you may find yourself
needing to implement a complex business rule that cannot easily be
implemented using standard SQL Server objects and T-SQL. So if you are
code savvy or have a code-savvy friend, you can create functions using CLR
to enforce complex rules.
Check out the authors' website where they co-host a podcast show for IT professionals.