Professional Documents
Culture Documents
Stored Procedures
and Triggers
Objectives
Understand the uses for triggers and learn how to write one.
11-1
TIP:
SQL Server uses stored procedures in most of its internal operations. System
stored procedures starting with the db_ prefix are located in each user
database, and system stored procedures starting with the sp_ prefix are
located in the master database. You can read the Transact-SQL of these stored
procedures by opening them in the Enterprise Manager or by running the
sp_helptext system stored procedure in the Query Analyzer and supplying the
stored procedure name. Youll even find that most of them contain
explanatory comments.
The only tasks that cannot be completed with a stored procedure are the
creation of triggers, defaults, rules, other stored procedures, and views. A
stored procedure can do everything from executing a basic SELECT statement
to enforcing complex business logic and explicit transactions.
11-2
11-3
Errors can be handled at the server, in the stored procedure, and not at
the client.
11-4
[WITH
{RECOMPILE | ENCRYPTION | RECOMPILE , ENCRYPTION}]
[FOR REPLICATION]
AS sql_statement [...n]
Stored procedure names must conform to the rules for identifiers, and must be
unique within the database for each owner. You can optionally add a number
after a semicolonthis allows you to create a group of procedures with the
same name and different numbers. You would need to use name;number to
call those procedure, but you could drop a group of procedures by using just
the name without a number.
The @parameter argument specifies the parameters for the stored procedure
and must be declared with a data type. Youre allowed up to 2,100 parameters
more than youll ever need. Youll learn about input and output parameters
later in this chapter.
If you set the RECOMPILE option, SQL Server will not cache a plan. The
ENCRYPTION option will encrypt the definition of the stored procedure.
One of the most important reasons to use stored procedures is their support for
both input and output parameters. This section covers creating stored
procedures and handling parameters.
11-5
The easiest way to get a stored procedure to return a value is to use a SELECT
statement. This stored procedure returns the results from the SELECT
statement, but it is very different from a view containing the same statement.
You cannot SELECT just certain rows or columns from the result set of a
stored procedure the way you can with a view. Also, you cannot INSERT,
UPDATE, or DELETE rows from the result set of a stored procedure. Those
actions must all take place within the stored procedure.
NOTE
11-6
or by position:
EXECUTE procEmployeeListByCity 'Seattle'
If you have more than one parameter, separate them with commas. If you
execute the stored procedure by position, the parameters must be supplied in
the order in which they are declared. If you execute the stored procedure by
name, the order is unimportant. Passing parameters by name makes your code
easier to read and maintain. On the other hand, passing values by position is
Microsoft SQL Server 2000 Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC
All rights reserved. Reproduction is strictly prohibited.
11-7
Code calling the stored procedure wont necessarily bomb if the parameter is
not supplied, if you have specified a default value. The following example uses
the ALTER PROC syntax to revise the original stored procedure. In this case,
NULL is supplied as the default value for the @City parameter. The stored
procedure code then tests the parameter value, branching to execute a different
query based on whether the input parameter is NULL or has actually been
supplied:
ALTER PROC procEmployeeListByCity
@City varchar(25) = NULL
AS
IF @City IS NULL
SELECT TOP 3
EmployeeID, LastName, FirstName, Address,
City, State, ZipCode
FROM tblEmployee
ELSE
SELECT EmployeeID, LastName, FirstName, Address,
City, State, ZipCode
FROM tblEmployee
WHERE City = @City
NOTE
When you execute the stored procedure without supplying a parameter, only
three rows are returned, since the TOP clause has limited the result set, as
shown in Figure 1.
11-8
EXEC procEmployeeListByCity
Figure 1. The result set from executing the parameterized stored procedure
without passing a parameter truncates the output at three rows.
When you execute the stored procedure with a parameter, then the WHERE
clause is applied and the matching rows are returned, as shown in Figure 2.
EXEC procEmployeeListByCity @City = 'Seattle'
Figure 2. The result set for employees who match the @City input parameter.
11-9
Output Parameters
Output parameters are used when you want to return a valuesuch as the new
identity column for an inserted record. Declare them normally, with the
OUTPUT keyword at the end of the declaration statement:
@EmployeeID int OUTPUT
11-10
TIP:
11-11
An alternate way is to return the new identity column in your stored procedure
as a result set, and not as an output parameter. The advantage to simply
returning it as a result set is that you dont have to declare and initialize an
output parameter, as shown in the following statement. However, output
parameters are slightly more efficient than creating a result set.
SELECT @@IDENTITY As NewID
TIP:
11-12
You cant use the SELECT statement to both assign variable values and select
data from a table at the same time. The following code snippet will cause an
error:
DECLARE @ID int
SELECT @ID = 1, FirstName, LastName FROM tblCustomer
You need to separate the operations and have two SELECT statements, one to
assign the value, and the other to create the result set:
DECLARE @ID int
SELECT @ID=1
SELECT FirstName, LastName
FROM tblCustomer
WHERE CustomerID = @ID
11-13
Simply double-click on the message and youll jump to the offending line of code.
This comes in handy when you have written a lot of code and arent sure where the
problem line is located.
SET NOCOUNT ON
Use SET NOCOUNT ON as the first line after AS within your stored
procedure. This eliminates the printed message of (xx rows(s) affected) in the
Query Analyzer window. It also eliminates a message of DONE_IN_PROC
that is communicated from SQL Server to the client application, which causes
another round trip across the network. Setting this option will not have any
impact on the value of @@ROWCOUNT.
The following example uses SET NOCOUNT ON and also returns the number
of rows affected by the query as a second result set:
CREATE PROC procEmployeeListNoCount
AS
SET NOCOUNT ON
SELECT EmployeeID, LastName, FirstName, Address,
City, State, ZipCode, HomePhone, url_EmailAddress
FROM tblEmployee
SELECT @@ROWCOUNT AS RowsReturned
Execute the procedure, and note the second result set shown in Figure 3.
11-14
EXEC procEmployeeListNoCount
-- Declare variables
DECLARE @Year int
11-15
-- Initialize counter
SET @Counter = 1
11-16
11-17
However, global temp tables are automatically dropped only when the connection that
created them disconnects and all other connections stop referencing them. Once the
connection that created the global temp table disconnects, connections initiated
subsequent to the closing of that connection will be unable to use the global temp
table. Connections using the global temp table that were initiated before its creator
disconnected can continue to use it.
11-18
Whichever you choose, you should be consistent. In addition, its a good idea
to pass back a message in the form of a varchar output parameter that returns
more detailed information.
11-19
The next issue is validating the input parameters. What if you dont want just
an EmployeeID with a bunch of nulls for LastName, FirstName, and all the
other fields in the table? That would be considered a garbage row by any
standards. In addition to creating NOT NULL constraints when you define
columns that should always contain data, you can also test for nulls in the
stored procedure before attempting an insert.
The first step is to validate that at least a FirstName and a LastName are
supplied. During validation, you can build up a message string and assign the
message code to return information if the parameters dont contain acceptable
values.
At the end of the tests, check the return code. If theres anything wrong, the
RETURN statement will unconditionally exit the stored procedure, passing
back as output parameters the return code of 0 and a return message indicating
the missing values:
SELECT @RetCode = NULL
SELECT @RetMsg = ''
IF @LastName IS NULL
SELECT @RetCode = 0,
@RetMsg = @RetMsg + 'Last Name Required. '
IF @FirstName IS NULL
SELECT @RetCode = 0,
@RetMsg = @RetMsg + 'First Name Required. '
IF @RetCode = 0
RETURN
The next part of the stored procedure does the actual insert:
INSERT INTO tblEmployee(
LastName, FirstName, Address,
City, State, ZipCode,
HomePhone)
VALUES(
11-20
There are three values you want to capture right after the INSERT statement:
@@ERROR, @@ROWCOUNT, and @@IDENTITY. Because @@ERROR
and @@ROWCOUNT are very fragile, they are best captured immediately
into local variables:
SELECT @Err = @@ERROR, @Rows = @@ROWCOUNT
The local @Err variable is then tested, and if an error has occurred, processing
jumps using the GOTO statement to the error handler.
IF (@Err <> 0) GOTO HandleErr
If there are no errors, the number of rows is checkedthis should be 1 (a nonzero value). If the row was successfully inserted, then the new EmployeeID is
retrieved via the @@IDENTITY function. If for some reason the new row did
not get inserted, then the failure code of 0 and the return message is passed
back to the client. Note that the RETURN statement unconditionally exits at
this point so that you dont fall into the error handling code at the bottom of
the procedure.
IF @Rows > 0
SELECT @EmployeeID = @@IDENTITY,
@RetCode = 1,
@RetMsg = 'New Employee Added'
ELSE
SELECT @EmployeeID = 0,
@RetCode = 0,
@RetMsg = 'New Employee Not Added'
RETURN
The error handler is at the end of the procedure and formulates the return code
and return message to pass back to the client:
11-21
HandleErr:
SELECT @EmployeeID = 0,
@RetCode = 0,
@RetMsg = 'Runtime Error: ' + CONVERT(VarChar, @Err)
RETURN
Figure 4 shows the result set displaying the return information from the stored
procedure.
Figure 4. The result set returning information from the stored procedure.
and a state of 1
RETURN 1
END
11-23
To retrieve the return value, declare a variable and use EXEC to assign the
return value of the procedure to that variable:
You can then use a SELECT statement to display the return value, along with
the new EmployeeID (if one was successfully created):
SELECT @RetC AS SuccessCode, @EmpID as NewEmployeeID
Rather than forcing you to use string concatenation to build your error
message, RAISERROR allows you to embed tokens in the message and to
supply values that will automatically be substituted for those tokens at runtime.
This follows the same pattern as the printf function in C or C++. In this
11-24
The error message that is returned automatically includes the values passed in
for FirstName and LastName:
"The new employee, Bullwinkle Moose, was NOT added."
Be sure to restore the default value of '' (a zero-length string) for
url_EmailAddress in tblEmployee, once you have tested the procedure.
11-25
Debugging
Once launched, youll be prompted to supply any parameters, as shown in
Figure 5. The Auto roll back option rolls back any changes you make during
the debugging session, allowing you to run your procedure without actually
changing any data. The debugger will then open in its own window.
Try It Out!
Follow these steps to test the debugger on the stored procedure created in the
previous example:
1. Launch the debugger on the procEmployeeInsertValidate stored
procedure, choosing either of the two methods outlined at the
beginning of this section.
2. Supply only the LastName parameter (as shown in Figure 5) and click
Execute. This launches the debugger in a new window, with the stored
procedure loaded and running.
11-26
11-27
11-28
7. You can restart with the same input parameters by clicking the Go
(F5) button, but there doesnt seem to be any way to supply fresh input
parameter or variable values.
8. When youre done stepping through code, simply close the window
and the debugger will go away.
The Transact-SQL Debugger is a much-needed tool. Its not quite as flexible
and useful as the debugging tools in Visual Studio, but after you use it for a
while, youll wonder how you ever wrote Transact-SQL code without it.
TIP:
11-29
Building Triggers
Triggers are always associated with tables or views, and cant be found as
independent objects in the Enterprise Manager. They can be found, however,
in the Object Browser, in a Triggers folder that appears for every table or view.
What Is a Trigger?
Triggers are procedures that run automatically in response to changes to your
data. The primary purpose of a trigger is to make a decision as to whether
these data changes should be committed to the database, but they can perform
any type of data manipulation action. There are three standard types of
triggers: INSERT, UPDATE, and DELETE. In SQL Server 2000, a new type
was addedINSTEAD OF. While earlier versions of SQL Server only
supported a single trigger of each type per table, SQL Server 7.0 supports
multiple triggers on the same table, and in SQL Server 2000 you can even
control which triggers fire first and last.
NOTE
In SQL Server 4.x, triggers were the only method available to enforce primary
key/foreign key relationships, also known as referential integrity. When SQL
Server 6.x was introduced, so was the ability to create foreign key constraints
that enforced referential integrity without triggers, called declarative referential
integrity (DRI). The role of the trigger moved primarily from enforcing
relationships to enforcing business rules that were too complex to be enforced
in a CHECK constraint. Some developers continued to use triggers for
referential integrity, because they were the only means for implementing
cascading updates and deletes in older versions of SQL Server. Now that SQL
Server 2000 can enforce the primary/foreign key relationships as well as
cascading updates and deletes, there is no need to use triggers to enforce
referential integrity.
Many developers try to avoid using triggers at all, because their hidden actions
can make maintenance and debugging very difficult, especially if the triggers
make changes to tables other than the one being explicitly updated. By forcing
all data changes to be made using stored procedures, you can avoid the need to
use triggers at all. If, however, you allow users and client applications to
directly update, insert, or delete data using ad hoc queries, then triggers are one
way to maintain control over your data.
11-30
Building Triggers
11-31
Trigger Syntax
The syntax for triggers is very much the same as for a stored procedure. The
primary difference between a stored procedure and a trigger is how the object
is executed. Stored procedures are explicitly called, while triggers are fired
automatically in response to a data modification.
CREATE TRIGGER trigger_name
ON {table|view}
[WITH ENCRYPTION]
{
{{FOR|AFTER|INSTEAD OF} {[DELETE][,][INSERT][,][UPDATE]}
[NOT FOR REPLICATION]
AS
[{IF UPDATE(column)
[{AND|OR} UPDATE(column)]
[...n]
| IF (COLUMNS_UPDATED() {bitwise_operator}
updated_bitmask)
{comparison_operator} column_bitmask [...n]
}]
sql_statement [...n]
}
}
Notice that there are no parameters that can be passed to a trigger. Triggers get
their source data from the inserted and deleted tables. The deleted table stores
copies of all rows that are to be impacted during a DELETE or UPDATE
operation. The inserted table holds new data for an INSERT or UPDATE.
It is common for update triggers to want to identify whether or not a certain
column has been changed. SQL Server provides the UPDATE(column)
argument to identify changed columns. You can call it multiple times to check
several columns. Another option is to use COLUMNS_UPDATED(), which
returns a single varbinary bit mask value where the first bit from the left (least
significant) indicates whether the first column was updated, the second bit
indicated the state of the second column, etc.
11-32
Building Triggers
Try It Out!
Follow these steps to create a trigger that disallows deleting a row of data from
a table:
1. Create a table named tblTest and insert two values into it, Stop and
Go.
CREATE TABLE tblTest
(Test varchar(50))
GO
11-33
IF @Test = 'Stop'
BEGIN
ROLLBACK TRAN
RAISERROR ('This record cannot be
deleted.',16,1)
END
GO
11-34
Building Triggers
5. Drop the table when youre done:
DROP TABLE tblTest
11-35
11-36
11-37
UPDATE tblCategory
SET tblCategory.Category =
(SELECT inserted.Category
FROM inserted)
WHERE tblCategory.Category =
(SELECT deleted.Category FROM deleted)
Although a single UPDATE statement that tried to update both the Product and
the Category tables through a view would otherwise fail, the INSTEAD OF
trigger fires instead of the normal UPDATE statement and explicitly writes
changes back to both tables. The user or client application doesnt even have to
know what the underlying tables are or how they are related. The update
statement will now succeed.
UPDATE vwProductByCategoryItrig
SET Product = 'Shark Thingys', Category = 'Thingys'
WHERE ProductID = 1
As you can see, INSTEAD OF triggers can make views very powerful indeed,
allowing actions that would not normally be permitted. You could also use
INSTEAD OF triggers to call stored procedures to perform the requested data
modification. This useful feature in SQL Server 2000 may tempt developers
who have been dead set against using triggers in the past to take a second look.
11-38
Summary
11-39
11-40
Questions
1. When does a stored procedure get compiled?
2. How can you prevent users from directly modifying data in tables?
3. How can you eliminate an extra network round-trip and the done-in-proc
message?
4. When do you have to trap errors in your stored procedures?
5. Can you write a trigger on a SELECT statement?
6. What does an INSTEAD OF trigger do?
11-41
Answers
1. When does a stored procedure get compiled?
On first execution
2. How can you prevent users from directly modifying data in tables?
Remove all permissions from tables, and use stored procedures,
granting EXECUTE permissions on the stored procedures.
3. How can you eliminate an extra network round-trip and the done-in-proc
message?
Use SET NOCOUNT ON as the first statement in your stored
procedure.
11-42
Lab 11:
Stored Procedures
and Triggers
TIP:
Because this lab includes a great deal of typed code, weve tried to make it
simpler for you. Youll find all the code in StoredProceduresLab.SQL, in
the same directory as the sample project. To avoid typing the code, you can
cut/paste it from the text file instead, or open the file as a script in the Query
Analyzer.
11-43
Lab 11:
Stored Procedures and Triggers
Lab 11 Overview
In this lab youll learn how to create a stored procedure and how to debug it
using the Transact-SQL Debugger.
To complete this lab, youll need to work through two exercises:
Each exercise includes an Objective section that describes the purpose of the
exercise. You are encouraged to try to complete the exercise from the
information given in the Objective section. If you require more information to
complete the exercise, the Objective section is followed by detailed step-bystep instructions.
11-44
Things to Consider
Step-by-Step Instructions
1. Start the SQL Query Analyzer, select the Shark database, and type the
procedure name followed by the input parameters:
CREATE PROC procCategoryAdd
@Category varchar(50) = NULL
@CategoryID int = NULL OUTPUT
@RetVal int = NULL OUTPUT
@RetMsg varchar(100) = NULL OUTPUT
AS
11-45
Lab 11:
Stored Procedures and Triggers
2. Declare a couple of variables to hold the @@ERROR and
@@ROWCOUNT values, and SET NOCOUNT ON to eliminate the
done-in-proc message:
DECLARE @ErrVal int
DECLARE @Rows int
SET NOCOUNT ON
3. Test to see if the input parameter is NULLif it is, exit the procedure by
issuing the RETURN statement after assigning the appropriate return value
and message to the output parameters:
IF @Category IS NULL
BEGIN
SELECT @RetVal = 0,
@RetMsg = 'Category not optional'
RETURN
END
11-46
BEGIN
SELECT @RetVal = 0,
@RetMsg = 'An error occurred'
RETURN
END
IF @Rows = 0
BEGIN
SELECT @RetVal = 0,
@RetMsg = 'Category was not inserted'
RETURN
END
ELSE
SELECT @CategoryId=@@IDENTITY,
@RetVal = 1, @RetMsg = 'Category inserted'
7. Check the syntax, and if everythings okay, press F5 to create the stored
procedure.
11-47
Lab 11:
Stored Procedures and Triggers
Things to Consider
Step-by-Step Instructions
1. Press F8 to load the Object Browser. Expand the Stored Procedure node
in the database.
2. Right-click on the procCategoryAdd stored procedure and select Debug
from the menu.
11-48
4. Type in @@ERROR in the Globals window. Click the Step Into (F11)
button to step to the next statement in the code.
11-49
Lab 11:
Stored Procedures and Triggers
5. As you step through the procedure, you should execute the RETURN
statement for not supplying an input parameter, as shown in Figure 10.
Figure 10. You should hit the RETURN statement because the @Category input
parameter was not supplied.
11-50
11-51
Lab 11:
Stored Procedures and Triggers
7. Step through the procedure. This time you should exit after the code has
determined that Shark Wear already exists in the table. Figure 12 shows
the debugger window after the procedure has exited.
Figure 12. The variable values for the input and output parameters are displayed
after the debugger has finished.
8. Close the debugger and start again, this time with a unique value for
@Category. Step through the code. Youll find that the record didnt
actually get inserted into the tblCategory table because the Auto rollback
option was selected when the debugger started.
9. To test the code from the Query Analyzer and see the return values, type
the following statements:
DECLARE
@CatID int
DECLARE
@Ret int
DECLARE
@Msg varchar(100)
11-52
Figure 13. The result set from executing the stored procedure.
11-53
Lab 11:
Stored Procedures and Triggers
11-54