Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2000 Forums
 SQL Server Development (2000)
 opinions on errorhandling?

Author  Topic 

Wanderer
Master Smack Fu Yak Hacker

1168 Posts

Posted - 2004-10-08 : 10:50:18
Hi guys,

Trying to get some opinions on errorhandling in SPs, and neatness/code structure. We are using .Net.


--sample table and data
create table TestDel
(loginId int identity(1,1),
val int)
go
insert into testdel (val)
select 1 union all select 2 union all select 3

select * from testdel


An SP using multi-returns


if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[up_testdel]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[up_testdel]
GO

SET QUOTED_IDENTIFIER OFF
SET ANSI_NULLS OFF
SET NOCOUNT ON
GO

CREATE PROCEDURE up_testdel
(@LoginID int)
AS
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

DECLARE @Error INTEGER --capture @@error
DECLARE @RowCount INTEGER --capture @@rowcount
--DECLARE @ReturnResult INTEGER --capture return value to hand back

--If we can use RaiseError:
-- DECLARE @DatabaseId INT
-- SET @DatabaseId = DB_ID()
--
-- DECLARE @DatabaseName NVARCHAR(128)
-- SET @DatabaseName = DB_NAME()

--We COULD consider adding "design by contract" by checking for valid value and number of rows returned,
-- i.e. select @ContractCheckLoginId = count(*) from dbo.TestDel
-- if @ContractCheckLoginId <> 1 the fail
-- but we don't have defined way of enabling atm the moment.

BEGIN TRANSACTION --unless you handle this outside
DELETE FROM dbo.TestDel WHERE Loginid = @LoginID
SELECT @RowCount=@@rowcount,@Error = @@error
IF @Error <> 0
-- Can we handle RaiseError messages?
BEGIN
ROLLBACK TRANSACTION --remove if transaction handled outside. NB then to use RETURN value to ensure rollback
-- RAISERROR ('Error deleting from table TestDel - rolled rack transaction',16,1,@DatabaseId,@DatabaseName)
RETURN -1 --Use -1 to indicate serious error processing SQL
END
IF @RowCount <> 1
BEGIN
ROLLBACK TRANSACTION --remove if transaction handled outside. NB then to use RETURN value to ensure rollback
-- RAISERROR ('Incorrect number of rows deleted - rolled rack transaction',16,1,@DatabaseId,@DatabaseName)
RETURN -2 --Use -2 to indicate incorrect number of rows affected. Breach of contract effectively.
END
ELSE
COMMIT TRANSACTION --remove if transaction handled outside. NB then to use RETURN value to ensure rollback
RETURN 0
GO


Or a goto structured SP:

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[up_testdel2]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[up_testdel2]
GO

SET QUOTED_IDENTIFIER OFF
SET ANSI_NULLS OFF
SET NOCOUNT ON
GO

CREATE PROCEDURE up_testdel2
(@LoginID int)
AS
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

DECLARE @Error INTEGER --capture @@error
DECLARE @RowCount INTEGER --capture @@rowcount
DECLARE @ReturnResult INTEGER --capture return value to hand back

--If we can use RaiseError:
-- DECLARE @DatabaseId INT
-- SET @DatabaseId = DB_ID()
--
-- DECLARE @DatabaseName NVARCHAR(128)
-- SET @DatabaseName = DB_NAME()

--We COULD consider adding "design by contract" by checking for valid value and number of rows returned,
-- i.e. select @ContractCheckLoginId = count(*) from dbo.TestDel
-- if @ContractCheckLoginId <> 1 the fail
-- but we don't have defined way of enabling atm the moment.

BEGIN TRANSACTION --unless you handle this outside
DELETE FROM dbo.TestDel WHERE Loginid = @LoginID
SELECT @RowCount=@@rowcount,@Error = @@error
IF (@Error = 0 and @RowCount = 1)
Begin
SET @ReturnResult = 0
COMMIT TRANSACTION
GOTO ReturnResult
END
IF @Error <> 0
-- Can we handle RaiseError messages?
BEGIN
ROLLBACK TRANSACTION --remove if transaction handled outside. NB then to use RETURN value to ensure rollback
-- RAISERROR ('Error deleting from table TestDel - rolled rack transaction',16,1,@DatabaseId,@DatabaseName)
SET @ReturnResult = @Error--Use -1 to indicate serious error processing SQL
GOTO ReturnResult
END
IF @RowCount <> 1
BEGIN
ROLLBACK TRANSACTION --remove if transaction handled outside. NB then to use RETURN value to ensure rollback
-- RAISERROR ('Incorrect number of rows deleted - rolled rack transaction',16,1,@DatabaseId,@DatabaseName)
SET @ReturnResult = -2 --Use -2 to indicate incorrect number of rows affected. Breach of contract effectively.
GOTO ReturnResult
END

ReturnResult:
RETURN @ReturnResult
GO


Test:

[]
set nocount on
DECLARE @lg int
DECLARE @Rc int

set @lg = 4
exec @rc=up_testdel @lg
select @rc
exec @rc=up_testdel2 @lg
select @rc

set @lg = 2
exec @rc=up_testdel @lg
select @rc
exec @rc=up_testdel2 @lg
select @rc
[/]

opinions on the better method of returning to caller the return?
opinions on the use of RaiseError?

CiaO

*##* *##* *##* *##*

Chaos, Disorder and Panic ... my work is done here!

X002548
Not Just a Number

15586 Posts

Posted - 2004-10-08 : 11:25:31
I like to do this...

http://weblogs.sqlteam.com/brettk/archive/2004/05/25/1378.aspx

Also, don't mess with the retun code value...SQL Server can override whatever you set, so it won't be reliable...



Brett

8-)
Go to Top of Page

Wanderer
Master Smack Fu Yak Hacker

1168 Posts

Posted - 2004-10-11 : 11:37:54
hmm - that's kinda scary, given the BOL example's:

USE Northwind
GO
-- Create a procedure that takes one input parameter
-- and returns one output parameter and a return code.
CREATE PROCEDURE SampleProcedure @EmployeeIDParm INT,
@MaxQuantity INT OUTPUT
AS
-- Declare and initialize a variable to hold @@ERROR.
DECLARE @ErrorSave INT
SET @ErrorSave = 0

-- Do a SELECT using the input parameter.
SELECT FirstName, LastName, Title
FROM Employees
WHERE EmployeeID = @EmployeeIDParm

-- Save any nonzero @@ERROR value.
IF (@@ERROR <> 0)
SET @ErrorSave = @@ERROR

-- Set a value in the output parameter.
SELECT @MaxQuantity = MAX(Quantity)
FROM [Order Details]

IF (@@ERROR <> 0)
SET @ErrorSave = @@ERROR

-- Returns 0 if neither SELECT statement had
-- an error, otherwise returns the last error.
RETURN @ErrorSave
GO


and

CREATE PROCEDURE checkstate @param varchar(11)
AS
IF (SELECT state FROM authors WHERE au_id = @param) = 'CA'
RETURN 1
ELSE
RETURN 2


Have you got reference to SQL overriding - I guess I need to elarn more about how/when that could happen...

Interesting, reading your TQL , I see you do use RaisError. When I use the GOTO method, I usually do the handling of the goto's slightly differently, have a GOTO _Error where I roll back, or else at the end of my successful code, after I commit, I GOTO Common_ExitPoint.

Eg:

begin tran
do update
if @@error <> 0
goto Error_label
commit tran
goto common_exit
Error_Label:
rollback transaction
raiserror

common_exit:
RETURN

I think it reads slightly cleaner, but I think your's might have slightly less operations to perform, and so might be slightly faster...

*##* *##* *##* *##*

Chaos, Disorder and Panic ... my work is done here!
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2004-10-11 : 12:41:25
Well I don't need to have a GOTO for the common exit. THe logic "falls through" to that point.

Only exceptions use GOTO.

Also on RETURN, I've posted sample code that shows the problem.

If you set the return interger value, AND you expect to interogate it and act accordingly, there is a possibility that you will take an incorrect action.



Brett

8-)
Go to Top of Page

Wanderer
Master Smack Fu Yak Hacker

1168 Posts

Posted - 2004-10-12 : 03:31:00
I see you get to the common exit with need for a goto, that why I think your's may be slightly more optimal.

Cool - will search for the examples on the RETURN problems... is it on your blog or here in the forums?

*##* *##* *##* *##*

Chaos, Disorder and Panic ... my work is done here!
Go to Top of Page
   

- Advertisement -