Tải bản đầy đủ (.pdf) (76 trang)

SQL Server 2000 Stored Procedure Programming phần 5 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (615.26 KB, 76 trang )

Chapter 7: Debugging and Error Handling
287
one additional parameter with the default set to 0 to the stored
procedure.
@debug int = 0
In the stored procedure, at all important points, I add code that
tests the value of the @debug variable and displays the values of
selected variables or resultsets:
if @debug <> 0
select @chvProperty Property,
@chvValue [Value],
@chvUnit [Unit]
. . .
if @debug <> 0
select * from #Properties
I do not use the Print statement for this purpose because
▼ It does not support the display of resultsets.

In older versions, it was impossible to concatenate a string
inside a Print statement.

Some utilities handle messages from the Print statement
differently than they do the resultset from the Select
statement.
In the following example, you can see a stored procedure that is
designed to support this kind of testing:
Alter Procedure prGetInventoryProperties_2
Return comma-delimited list of properties
which are describing asset.
i.e.: Property=Value unit;Property=Value unit; (
@intInventoryId int,


@chvProperties varchar(8000) OUTPUT,
@debug int = 0
)
As
288
SQL Server 2000 Stored Procedure Programming
set nocount on
declare @intCountProperties int,
@intCounter int,
@chvProperty varchar(50),
@chvValue varchar(50),
@chvUnit varchar(50),
@insLenProperty smallint,
@insLenValue smallint,
@insLenUnit smallint,
@insLenProperties smallint
declare @chvProcedure sysname
set @chvProcedure = 'prGetInventoryProperties_2'
if @debug <> 0
select '**** '+ @chvProcedure + 'START ****'
Create table #Properties(Id int identity(1,1),
Property varchar(50),
Value varchar(50),
Unit varchar(50))
identify Properties associated with asset
insert into #Properties (Property, Value, Unit)
select Property, Value, Unit
from InventoryProperty inner join Property
on InventoryProperty.PropertyId = Property.PropertyId
where InventoryProperty.InventoryId = @intInventoryId

if @debug <> 0
select * from #Properties
set loop
select @intCountProperties = Count(*),
@intCounter = 1,
Chapter 7: Debugging and Error Handling
289
@chvProperties = ''
from #Properties
loop through list of properties
while @intCounter <= @intCountProperties
begin
get one property
select @chvProperty = Property,
@chvValue = Value,
@chvUnit = Coalesce(Unit, '')
from #Properties
where Id = @intCounter
if @debug <> 0
select @chvProperty Property,
@chvValue [Value],
@chvUnit [Unit]
check will new string fit
select @insLenProperty = DATALENGTH(@chvProperty),
@insLenValue = DATALENGTH(@chvValue),
@insLenUnit = DATALENGTH(@chvUnit),
@insLenProperties = DATALENGTH(@chvProperties)
if @insLenProperties + 2
+ @insLenProperty + 1
+ @insLenValue + 1

+ @insLenUnit > 8000
begin
select 'List of properties is too long '
+ '(over 8000 characters)!'
return 1
end
assemble list
set @chvProperties = @chvProperties + @chvProperty
290
SQL Server 2000 Stored Procedure Programming
+ '=' + @chvValue + ' ' + @chvUnit + '; '
if @debug <> 0
select @chvProperties chvProperties
let's go another round and get another property
set @intCounter = @intCounter + 1
end
drop table #Properties
if @debug <> 0
select '**** '+ @chvProcedure + 'END ****'
return 0
Execution in a Test Environment
To debug or test a stored procedure, I
execute the stored procedure from Query Analyzer with the @debug
parameter set to 1.
declare @chvResult varchar(8000)
exec prGetInventoryProperties
@intInventoryId = 5,
@chvProperties = @chvResult OUTPUT,
@debug = 1
select @chvResult Result

Naturally, you can pass parameters either by name or by position.
The result of the execution will be an elaborate printout like the one
shown in Figure 7-6.
Execution in the Production Environment
In production, the stored
procedure is called without a reference to the @debug parameter.
Here, SQL Server assigns a default value to the parameter (0), and
the stored procedure is executed without debug statements.
exec prGetInventoryProperties
@intInventoryId = 5,
@chvProperties = @chvResult OUTPUT
Chapter 7: Debugging and Error Handling
291
Nested Stored Procedures
Two tricks can help you debug a set of
nested stored procedures (that is, when a stored procedure calls
another stored procedure). It is a useful practice to display the
name of the stored procedure at the beginning and end of the
stored procedure.
declare @chvProcedure sysname
set @chvProcedure = 'prGetInventoryProperties_2'
if @debug <> 0
select '**** '+ @chvProcedure + 'START ****'

if @debug <> 0
select '**** '+ @chvProcedure + 'END ****'
return 0
Figure 7-6. Poor Man’s Debugger
292
SQL Server 2000 Stored Procedure Programming

When you call a nested stored procedure, you need to pass the
value of the @debug parameter to it as well. In this way, you will be
able to see its debugging information.
exec prGetInventoryProperties @intInventoryId,
@chvProperties OUTPUT,
@debug
Typical Errors
You should keep the following issues in mind when you are writing
your code and testing Transact-SQL programs:

NULLS

Assignment of variable from resultset

No records affected
■ Wrong size or datatype
■ Default length
■ Rollback of triggers
■ Warnings and lower-priority errors

Nested comments

Deferred name resolution

Cursors

Overconfidence
NULLs
Many errors are a result of the inadequate treatment of NULL values
in Transact-SQL code. Developers often forget that local variables or

table columns might contain NULLs. If such a value becomes part of
any expression, the result will also be NULL.
The proper way to test the value of an expression for NULLs is to
use the IS NULL or IS NOT NULL clauses. Microsoft SQL Server treats
Chapter 7: Debugging and Error Handling
293
the use of = NULL as another way to type IS NULL, but <> NULL is
not the equivalent of IS NOT NULL. The result of such an expression
is always simply NULL. It will never be true, and stored procedures
will always skip statements after the If statement when you use
the <> NULL clause.
If @intInventoryId IS NULL

If @intInventoryId = NULL

If @intInventoryId IS NOT NULL

If @intInventoryId <> NULL WRONG!!!

Assignment of Variable from Resultset
Earlier, we discussed assigning the value(s) for a variable(s) using
the resultset of the Select statement. This technique is fine when the
resultset returns precisely one record. However, if the resultset returns
more than one record, the variable(s) are assigned using the value(s)
from the last record in recordset. Not perfect, but in some cases, you
can live with it. It is sometimes difficult to predict which record will be
returned as last in the recordset. It depends on the query and the index
that SQL Server has used.
A more serious problem occurs when the recordset is empty.
The values of the variables are changed in this case, and the code is

vulnerable to several mistakes. If you do not expect the resultset to be
empty, your stored procedure will fail. If you expect the values of the
variables to be NULL, your stored procedure will function correctly
only immediately after it is started (that is, in the first iteration of the
process). In such a case, the local variables are not yet initialized
and will contain NULLs. Later, when variables are initialized, their
values will remain unchanged. If you are testing the contents of the
variables for NULLs to find out if the record was selected, you will
just process the previous record again.
294
SQL Server 2000 Stored Procedure Programming
No Records Affected
Developers sometimes assume that SQL Server will return errors if a
Transact-SQL statement affects no records. Unfortunately, this error
is semantic rather than syntactic and SQL Server will not detect it.
In order to determine such an error, use the @@rowcount
function rather than the @@error function:
declare @intRowCount int
declare @intErrorCode int
update Inventory
Set StatusId = -3
where AssetId = -11
select @intRowCount = @@rowCount,
@intErrorCode = @@Error
if @@rowCount = 0
begin
select "Record was not updated!"
return 50001
end
Wrong Size or Datatype

I can recall one occasion when a colleague of mine spent two days
going through a complicated data conversion process to find out
why his process was consistently failing. In one of the nested stored
procedures, I had declared the variable as tinyint instead of
int. During the testing phase of the project, everything worked
perfectly, because the variable was never set to a value higher
than 255. However, a couple of months later in production, the
process started to fail as values climbed higher.
Similar problems can occur if you do not fully understand
the differences between similar formats (for example, char and
varchar, money and smallmoney), or if you fail to synchronize
Chapter 7: Debugging and Error Handling
295
the sizes of datatypes (for instance, char, varchar, numeric, and
other datatypes of variable size).
Default Length
A similar problem can occur when a developer does not supply
the length of the variable datatype and SQL Server assigns a
default length.
For example, the default length of the varchar datatype is 30.
Most of the time SQL Server reports an error if the length is omitted,
but not in all cases. In the Convert function, for example, the user
need only specify the datatype:
Convert(varchar, @intPropertyId)
If the resulting string is short enough, you will not have any
problems. I recall a colleague who employed this method for years
without any problems, and then….
Unfortunately, other statements and functions behave as
expected. If you declare a variable and assign it like so:
Declare @test varchar

Set @test = '123456789012345678901234567890'
Select datalength(@test), @test
SQL Server will allocate just one byte to the string and return the
following:

1 1
(1 row(s) affected)
Rollback of Triggers
In different versions of SQL Server, triggers react differently in rollback
transaction statements. When a trigger is rolled back in SQL Server 7.0
or SQL Server 2000, the complete batch that initiated the trigger fails
and the execution continues from the first statement of the next batch.
Version 4.2 behaves in a similar manner. In version 6.0, processing
continues in the trigger, but the batch is canceled. In version 6.5, the
processing continues in both the trigger and the batch. It was the
responsibility of the developer to detect errors and cascade out of
the process.
Warnings and Lower Priority Errors
Warnings do not stop the execution of a stored procedure. In fact,
you cannot even detect them from within the SQL Server
environment.
Low-level errors, which are detectable using the @@error
function, do not abort the execution either. Unfortunately, there
are also errors that abort processing completely, so that the error
handlers in stored procedures do not process the error.
Nested Comments
Only single line comments ( ) can be nested. Nested multiline
comments (/* */) may be treated differently by different client tools.
I recommend that you put one or two stars (**) at the beginning of
each line that is commented out. In this manner, the problem will be

obvious if the comments are nested and SQL Server starts to compile
part of the code that you consider to be commented out.
/************************************************************
** select *
** from #Properties
*************************************************************/
Deferred Name Resolution
It is possible (in Microsoft SQL Server 7.0 and Microsoft SQL
Server 2000) to create database objects (such as stored procedures
and triggers) that refer to other database objects that do not exist
within the database. In previous versions, such attempts were treated
as syntax errors. This feature helps tremendously when you need to
generate a database structure and objects using script. Unfortunately,
that introduces a number of risks. If you make a typo in the name
296
SQL Server 2000 Stored Procedure Programming
of the table from which you want to retrieve records, SQL Server
will not report a syntax error during compilation but will report a
runtime error during execution.
Create Procedure prDeferredNameResolution
As
set nocount on
select 'Start'
select * from NonExistingTable
select 'Will execution be stopped?'
return
If you attempt to run this stored procedure, SQL Server will return
the following:

Start

Server: Msg 208, Level 16, State 1,
Procedure prDeferredNameResolution, Line 7
Invalid object name 'NonExistingTable'.
The execution will be stopped. Even an error handler written in
Transact-SQL will not be able to proceed at this point.
Cursors
Be very cautious when you use cursors. Test the status after each
fetch; place error handling after each command; do not forget to
close and deallocate the cursor when you do not need it any more.
There are many rules and regulations for using cursors, and some of
them might seem trivial, but even the smallest mistake can halt the
execution of your code.
Overconfidence
The overconfidence that comes with routine may be your worst
enemy. If you perform the same or similar tasks over and over
again, you can lose focus and skip basic steps. Do not put code into
production before it is thoroughly tested; do not place bug fixes
Chapter 7: Debugging and Error Handling
297
directly into production; use error handling even if the code seems
straightforward and the chance for error slight.
ERROR HANDLING
A developer’s effective use of error handling procedures is
often an excellent indicator of his or her seniority in that particular
programming language. Those of us who deal with a C or Visual
Basic environment are accustomed to a whole set of feature-rich error
handling objects, procedures, and functions. Compared with them,
TSQL seems rather inadequate. The developer can employ only one
global variable and a few procedures for setting or raising errors.
However, the apparent inadequacy of the tool set cannot justify

sloppy solutions.
In this section, we will discuss the concept of error handling and
offer a coherent methodology for its implementation. We will also
discuss some alternative techniques involving the XACT_ABORT
and Raiserror statements.
Using Error Handling
Since TSQL is so laconic (critics may say feature poor), development
DBAs commonly express themselves in a very concise manner. DBAs
frequently write ad hoc scripts for one-time use or manual execution,
and they thus neglect the need for consistent error handling.
Logic that is fine in standard languages like Visual Basic or C
frequently does not work in TSQL. For example, an error may occur
in TSQL, but if TSQL does not consider it fatal, processing will
continue. Also, if the error is fatal, all processing will stop. The
process does not react: it is just killed.
Why Bother?
For many, the question is why be concerned with implementing
error handling at all? Let us review this question through the
following example:
298
SQL Server 2000 Stored Procedure Programming
Create Procedure prInsertLeasedAsset_1
Insert leased asset and update total in LeaseSchedule.
(demonstration of imperfect solution)
(
@intEquipmentId int,
@intLocationId int,
@intStatusId int,
@intLeaseId int,
@intLeaseScheduleId int,

@intOwnerId int,
@mnyLease money,
@intAcquisitionTypeID int
)
As
set nocount on
begin transaction
insert asset
insert Inventory(EquipmentId, LocationId,
StatusId, LeaseId,
LeaseScheduleId, OwnerId,
Lease, AcquisitionTypeID)
values ( @intEquipmentId, @intLocationId,
@intStatusId, @intLeaseId,
@intLeaseScheduleId,@intOwnerId,
@mnyLease, @intAcquisitionTypeID)
update total
update LeaseSchedule
Set PeriodicTotalAmount = PeriodicTotalAmount + @mnyLease
where LeaseId = @intLeaseId
commit transaction
return
Chapter 7: Debugging and Error Handling
299
300
SQL Server 2000 Stored Procedure Programming
This may seem a trivial example, and it is true that in all
probability nothing would go wrong, but let’s imagine an error
occurs on the Update statement. The error could be for any
reason—overflow, some constraint, or inadequate permission, for

example. As explained earlier, transactions do not roll back on their
own when an error occurs. Instead, SQL Server simply commits
everything that was changed when it encounters the Commit
Transaction statement as if nothing unusual had happened.
Unfortunately, from that moment on, the total of the lease schedule
will have the wrong value.
Tactics of Error Handling
Some DBAs recognize the importance of this issue and place error
handling in critical positions in their code. The result would be
something like the following:
Create Procedure prInsertLeasedAsset_2
Insert leased asset and update total in LeaseSchedule.
(demonstration of not exactly perfect solution)
(
@intEquipmentId int,
@intLocationId int,
@intStatusId int,
@intLeaseId int,
@intLeaseScheduleId int,
@intOwnerId int,
@mnyLease money,
@intAcquisitionTypeID int
)
As
set nocount on
begin transaction
insert asset
insert Inventory(EquipmentId, LocationId,
StatusId, LeaseId,
LeaseScheduleId, OwnerId,

Chapter 7: Debugging and Error Handling
301
Lease, AcquisitionTypeID)
values ( @intEquipmentId, @intLocationId,
@intStatusId, @intLeaseId,
@intLeaseScheduleId,@intOwnerId,
@mnyLease, @intAcquisitionTypeID)
If @@error <> 0
Begin
Print 'Unexpected error occurred!'
Rollback transaction
Return 1
End
update total
update LeaseSchedule
Set PeriodicTotalAmount = PeriodicTotalAmount + @mnyLease
where LeaseId = @intLeaseId
If @@error <> 0
Begin
Print 'Unexpected error occurred!'
Rollback transaction
Return 1
End
commit transaction
return 0
This kind of solution contains substantial repetition—especially if
your business logic requires more than two Transact-SQL statements
to be implemented. A more elegant solution is to group codes into a
generic error handling procedure:
Create Procedure prInsertLeasedAsset_3

Insert leased asset and update total in LeaseSchedule.
(demonstration of not exactly perfect solution)
(
@intEquipmentId int,
@intLocationId int,
@intStatusId int,
@intLeaseId int,
@intLeaseScheduleId int,
@intOwnerId int,
@mnyLease money,
@intAcquisitionTypeID int
)
As
set nocount on
begin transaction
insert asset
insert Inventory(EquipmentId, LocationId,
StatusId, LeaseId,
LeaseScheduleId, OwnerId,
Lease, AcquisitionTypeID)
values ( @intEquipmentId, @intLocationId,
@intStatusId, @intLeaseId,
@intLeaseScheduleId,@intOwnerId,
@mnyLease, @intAcquisitionTypeID)
If @@error <> 0 GOTO ERR_HANDLER
update total
update LeaseSchedule
Set PeriodicTotalAmount = PeriodicTotalAmount + @mnyLease
where LeaseId = @intLeaseId
If @@error <> 0 GOTO ERR_HANDLER

commit transaction
return 0
ERR_HANDLER:
Print 'Unexpected error occurred!'
Rollback transaction
Return 1
302
SQL Server 2000 Stored Procedure Programming
Chapter 7: Debugging and Error Handling
303
This is better, but it does not deal with all of the issues that need to
be handled.
A typical error that beginners in TSQL make is to check the
value of a global variable and then try to return or process it. Such an
attempt is usually the result of a good intention such as wanting to
notify the user of an error that has occurred.
Create Procedure prInsertLeasedAsset_4
Insert leased asset and update total in LeaseSchedule.
(demonstration of not exactly perfect solution)
(
@intEquipmentId int,
@intLocationId int,
@intStatusId int,
@intLeaseId int,
@intLeaseScheduleId int,
@intOwnerId int,
@mnyLease money,
@intAcquisitionTypeID int
)
As

set nocount on
begin transaction
insert asset
insert Inventory(EquipmentId, LocationId,
StatusId, LeaseId,
LeaseScheduleId, OwnerId,
Lease, AcquisitionTypeID)
values ( @intEquipmentId, @intLocationId,
@intStatusId, @intLeaseId,
@intLeaseScheduleId,@intOwnerId,
@mnyLease, @intAcquisitionTypeID)
If @@error <> 0 GOTO ERR_HANDLER
update total
update LeaseSchedule
Set PeriodicTotalAmount = PeriodicTotalAmount + @mnyLease
where LeaseId = @intLeaseId
If @@Error <> 0 GOTO ERR_HANDLER
commit transaction
return 0
ERR_HANDLER:
Print 'Unexpected error occurred: '
+ Convert(varchar, @@Error) –- this will
not work,
as expected
Rollback transaction
Return @@Error
Although something like this could work in Visual Basic, for
example, in this case the stored procedure will return 0 as an error
number. SQL Server sets the value of the @@Error variable after each
statement. It treats each statement separately, so the

value of @@Error is set to 0 subsequently when the If statement is
(successfully) executed. Thus the Print statement displays 0 as an
error number, and eventually the stored procedure will also return 0.
A Coherent Error Handling Methodology
Let’s discuss a single coherent error handling methodology.
The fundamental idea is that all SQL statements within a stored
procedure should be covered by this error handling solution. Any
time an unexpected error occurs, a stored procedure should stop
further processing. When the current stored procedure stops
processing, so should the stored procedures that called it.
The basic feature of this solution is to follow all SQL statements
with a statement that reads the contents of the @@Error variable,
along with an If statement, which checks whether the previous
command completed successfully.
304
SQL Server 2000 Stored Procedure Programming
Create Procedure prInsertLeasedAsset_5
Insert leased asset and update total in LeaseSchedule.
(
@intEquipmentId int,
@intLocationId int,
@intStatusId int,
@intLeaseId int,
@intLeaseScheduleId int,
@intOwnerId int,
@mnyLease money,
@intAcquisitionTypeID int
)
As
set nocount on

Declare @intErrorCode int
Select @intErrorCode = @@Error
begin transaction
If @intErrorCode = 0
begin
— insert asset
insert Inventory(EquipmentId, LocationId,
StatusId, LeaseId,
LeaseScheduleId, OwnerId,
Lease, AcquisitionTypeID)
values ( @intEquipmentId, @intLocationId,
@intStatusId, @intLeaseId,
@intLeaseScheduleId,@intOwnerId,
@mnyLease, @intAcquisitionTypeID)
Select @intErrorCode = @@Error
end
If @intErrorCode = 0
begin
— update total
Chapter 7: Debugging and Error Handling
305
update LeaseSchedule
Set PeriodicTotalAmount = PeriodicTotalAmount + @mnyLease
where LeaseId = @intLeaseId
Select @intErrorCode = @@Error
end
If @intErrorCode = 0
COMMIT TRANSACTION
Else
ROLLBACK TRANSACTION

return @intErrorCode
If an error occurs, the If statements prevent further execution
of the business logic and pass an error to the end of the procedure.
Changes will be rolled back, and the stored procedure returns
the value of the @intErrorCode variable to the calling stored
procedure or script. If an error occurs, this variable may be used
to notify the calling procedure that there was a problem.
Nested Stored Procedures
The calling stored procedure might have the same error handling
system in place. In such a case, calls to the stored procedures should
treat the returned values as error codes:

If @ErrorCode = 0
Begin
execute @intErrorCode = MyStoredProcedure @parm1, @param2…
End
The method works like a cascade that stops all further processing in a
whole set of nested stored procedures.
Interfacing to Other Environments
This error handling structure is very useful even in cases when a
stored procedure is called from another programming environment,
such as Visual Basic or Visual C++. The return value of a stored
306
SQL Server 2000 Stored Procedure Programming
Chapter 7: Debugging and Error Handling
307
procedure can be retrieved, and an error can be handled on that
level as well.
conn.Open "provider=sqloledb;data source=sqlserver;" + _
"user id=sa;password=;initial catalog=Asset"

With cmd
Set .ActiveConnection = conn
.CommandText = "prInsertLeasedAsset_5"
.CommandType = adCmdStoredProc
.Parameters.Refresh
.parameters(1).Value = 4
.parameters(2).Value = 1
.parameters(3).Value = 1
.parameters(4).Value = 1
.parameters(5).Value = 1
.parameters(6).Value = 1
.parameters(7).Value = 99.95
.parameters(8).Value = 1
Set rs = .Execute()
lngReturnValue = .Parameters(0).Value
end with
If lngReturnValue <> 0 Then
MsgBox "Procedure have failed!"
Exit Sub
Else
MsgBox "Procedure was successful"
end if
Other Global Variables
Cases should be handled with the same Select statement that
reads @@Error when you wish to read the value of some other
global variables immediately after the statement. You often
require such a technique when you are using identity columns.
insert Inventory(EquipmentId, LocationId,
StatusId, LeaseId,
LeaseScheduleId, OwnerId,

Lease, AcquisitionTypeID)
values ( @intEquipmentId, @intLocationId,
@intStatusId, @intLeaseId,
@intLeaseScheduleId,@intOwnerId,
@mnyLease, @intAcquisitionTypeID)
Select @intErrorCode = @@Error,
@intInventoryId = @@identity
Transaction Processing
You can integrate transaction processing perfectly with this solution.
Review Chapter 5 to remind yourself why Rollback and Commit
must be treated differently.
At the beginning of a stored procedure or transaction, the
developer should add the following code:
Declare @intTransactionCountOnEntry int
If @intErrorCode = 0
Begin
Select @intTransactionCountOnEntry = @@TranCount
BEGIN TRANSACTION
End
At the end of the procedure (and/or transaction), the developer
should complete the transaction:
If @@TranCount > @intTransactionCountOnEntry
Begin
If @intErrorCode = 0
COMMIT TRANSACTION
Else
ROLLBACK TRANSACTION
End
The solution will also perform well in the case of nested stored
procedures. All procedures are rolled back using the same cascading

mechanism.
The local variable @TransactionCountOnEntry is used to
track the number of opened transactions upon entry into a stored
procedure. If the number is unaffected within the stored procedure,
308
SQL Server 2000 Stored Procedure Programming
there is no reason either to Commit or Rollback within the
procedure. The finished stored procedure looks like this:
Alter Procedure prInsertLeasedAsset_6
Insert leased asset and update total in LeaseSchedule.
(
@intEquipmentId int,
@intLocationId int,
@intStatusId int,
@intLeaseId int,
@intLeaseScheduleId int,
@intOwnerId int,
@mnyLease money,
@intAcquisitionTypeID int,
@intInventoryId int OUTPUT
)
As
set nocount on
Declare @intErrorCode int,
@intTransactionCountOnEntry int
Select @intErrorCode = @@Error
If @intErrorCode = 0
Begin
Select @intTransactionCountOnEntry = @@TranCount
BEGIN TRANSACTION

End
If @intErrorCode = 0
begin
insert asset
insert Inventory(EquipmentId, LocationId,
StatusId, LeaseId,
LeaseScheduleId, OwnerId,
Lease, AcquisitionTypeID)
values ( @intEquipmentId, @intLocationId,
Chapter 7: Debugging and Error Handling
309
310
SQL Server 2000 Stored Procedure Programming
@intStatusId, @intLeaseId,
@intLeaseScheduleId,@intOwnerId,
@mnyLease, @intAcquisitionTypeID)
Select @intErrorCode = @@Error,
@intInventoryId = @@identity
end
If @intErrorCode = 0
begin
update total
update LeaseSchedule
Set PeriodicTotalAmount = PeriodicTotalAmount + @mnyLease
where LeaseId = @intLeaseId
Select @intErrorCode = @@Error
end
If @@TranCount > @intTransactionCountOnEntry
Begin
If @@Error = 0

COMMIT TRANSACTION
Else
ROLLBACK TRANSACTION
End
return @intErrorCode
XACT_ABORT
SQL Server does, in fact, have an equivalent to the On Error Go To
command used by Visual Basic. The SET XACT_ABORT statement
forces SQL Server to roll back the complete transaction and stop
further processing on the occurrence of any error.
create Procedure prInsertLeasedAsset_7
Insert leased asset and update total in LeaseSchedule.
(demonstration of imperfect solution)
(
@intEquipmentId int,
@intLocationId int,
@intStatusId int,
@intLeaseId int,
@intLeaseScheduleId int,
@intOwnerId int,
@mnyLease money,
@intAcquisitionTypeID int
)
As
set nocount on
SET XACT_ABORT ON
begin transaction
insert asset
insert Inventory(EquipmentId, LocationId,
StatusId, LeaseId,

LeaseScheduleId, OwnerId,
Lease, AcquisitionTypeID)
values ( @intEquipmentId, @intLocationId,
@intStatusId, @intLeaseId,
@intLeaseScheduleId,@intOwnerId,
@mnyLease, @intAcquisitionTypeID)
update total
update LeaseSchedule
Set PeriodicTotalAmount = PeriodicTotalAmount + @mnyLease
where LeaseId = @intLeaseId
commit transaction
return (0)
Unfortunately, this solution presents a problem. This statement
will also completely stop execution of the current batch. The error
can still be detected and handled from the client application, but
inside the Transact-SQL code, SQL Server will treat it as a fatal error.
Chapter 7: Debugging and Error Handling
311

×