I have a really simple query like:
BEGIN TRY
BEGIN TRAN;
DECLARE #CurrentPassword VARCHAR(255) = (SELECT TOP 1 [Password]
FROM Employee
WHERE #EmpGuid = EmpGuid)
IF (#Password = #CurrentPassword)
UPDATE [Employee]
SET [Password] = #NewPassword
WHERE #EmpGuid = EmpGuid
SELECT 1;
ELSE
SELECT 2;
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
But I really don't know why in my else clause I get
Incorrect syntax near 'ELSE'
What am I doing wrong? Regards
You you want to have more than one statement after the IF, you must use a BEGIN .... END block - like this:
BEGIN TRY
BEGIN TRAN;
DECLARE #CurrentPassword VARCHAR(255) = (SELECT TOP 1 [Password]
FROM Employee
WHERE #EmpGuid = EmpGuid)
IF (#Password = #CurrentPassword)
BEGIN --- you need a *BEGIN* here!!!
UPDATE [Employee]
SET [Password] = #NewPassword
WHERE #EmpGuid = EmpGuid
SELECT 1;
END --- and the *END* for your new *BEGIN*
ELSE
SELECT 2;
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
Use explicitly begin . . end
BEGIN TRY
BEGIN TRAN
DECLARE #CurrentPassword VARCHAR(255) = (SELECT TOP 1 [Password]
FROM Employee
WHERE #EmpGuid = EmpGuid)
IF (#Password = #CurrentPassword)
BEGIN
UPDATE [Employee]
SET [Password] = #NewPassword
WHERE #EmpGuid = EmpGuid
SELECT 1
END
ELSE
BEGIN
SELECT 2
END
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
Sup guys, is it possible for INSERT or UPDATE to throw an exception that would stop the procedure? I'm in a little bit of a pickle, because i've hanging transactions in what seems to be a bullet proof code.
BEGIN TRANSACTION;
SET #sSystemLogDataId = CONVERT(NCHAR(36), NEWID());
INSERT INTO crddata.crd_systemlogdata (systemdataid,systemlogid,userid,
actiondatetime,actionstate)
VALUES(#sSystemLogDataId,#inSystemLogId,#sUserId,GETDATE(),#nActionState);
SET #nError = ##ERROR;
IF (1 = #nChangeMassprintTaskStatus) AND (0 = #nError)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttaskstatus=#nMassprintTaskStatus
WHERE massprinttaskid = #inMassprintTaskId;
SET #nError = ##ERROR;
END
IF (#MassprintTaskType <> 1) AND (27 = #nActionState) AND (0 = #nError)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttasktype=1
WHERE massprinttaskid = #inMassprintTaskId;
SET #nError = ##ERROR;
END
IF 0 = #nError
BEGIN
COMMIT TRANSACTION;
END
ELSE
BEGIN
ROLLBACK TRANSACTION;
END
Halp, anyone?
Without TRY/CATCH, this is not bullet proof.
Errors can be batch aborting (eg datatype conversions or errors thrown from triggers) which means ROLLBACK does not run.
You have to use TRY/CATCH and I always use SET XACT_ABORT ON too
SET XACT_ABORT, NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
SET #sSystemLogDataId = CONVERT(NCHAR(36), NEWID());
INSERT INTO crddata.crd_systemlogdata (systemdataid,systemlogid,userid,
actiondatetime,actionstate)
VALUES(#sSystemLogDataId,#inSystemLogId,#sUserId,GETDATE(),#nActionState);
IF (1 = #nChangeMassprintTaskStatus)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttaskstatus=#nMassprintTaskStatus
WHERE massprinttaskid = #inMassprintTaskId;
END
IF (#MassprintTaskType <> 1) AND (27 = #nActionState)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttasktype=1
WHERE massprinttaskid = #inMassprintTaskId;
END
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 --may already be rolled back by SET XACT_ABORT or a trigger
ROLLBACK TRANSACTION;
RAISERROR [rethrow caught error using ERROR_NUMBER(), ERROR_MESSAGE(), etc]
END CATCH
Mandatory background reading is Erland Sommarskog's "Error Handling in SQL 2005 and Later": we'll test you on it later...
Create a trigger that will raise an exception if insert/update is not correct
example:
create table t (id int)
go
create trigger tr on t
for insert
as
if exists(select 1 from inserted where id = 0)
raiserror('id is not valid', 16, 1)
go
insert t select 1
select ##error
insert t select 0
select ##error
The following code works like a charm:
BEGIN TRY
BEGIN TRANSACTION
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK;
DECLARE #ErrorMessage NVARCHAR(4000),
#ErrorSeverity int;
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY();
RAISERROR(#ErrorMessage, #ErrorSeverity, 1);
END CATCH
But this code gives an error:
BEGIN TRY
BEGIN TRANSACTION
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK;
RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), 1);
END CATCH
Why?
RAISERROR() can not take calls as its parameters. Needs to be constants or variables.
+1 The RAISERROR statement generates an error message by either retrieving the message from the sys.messages catalog view or constructing the message string at runtime. So agreeing with the fellow #Mitch Wheat I will go with his recommendation.
I'm having a problem with TRY...CATCH blocks. Can someone explain why the following code will not execute my sp?
DECLARE #Result int
SET #Result = 0
BEGIN TRY
SELECT * FROM TableNoExist
END TRY
BEGIN CATCH
SET #Result = ERROR_NUMBER()
END CATCH
EXEC dbo.spSecurityEventAdd #pSecurityEventTypeID = 11, #pResult = #Result
But this code does work:
DECLARE #Result int
SET #Result = 0
BEGIN TRY
SELECT 1/0
END TRY
BEGIN CATCH
SET #Result = ERROR_NUMBER()
END CATCH
EXEC dbo.spSecurityEventAdd #pSecurityEventTypeID = 11, #pResult = #Result
I'd like to make sure I catch all errors.
Thanks
Compile and Statement-level Recompile Errors
There are two types of errors that will not be handled by TRY…CATCH if the error occurs in the same execution level as the TRY…CATCH construct:
Compile errors, such as syntax errors that prevent a batch from executing.
Errors that occur during statement-level recompilation, such as object name resolution errors that happen after compilation due to deferred name resolution.
http://msdn.microsoft.com/en-us/library/ms179296.aspx
It looks like this thread answers your question.
The title really is the question for this one: Is there an equivalent in T-SQL to C#'s "throw;" to re-throw exceptions?
In C# one can do this:
try
{
DoSomethingThatMightThrowAnException();
}
catch (Exception ex)
{
// Do something with the exception
throw; // Re-throw it as-is.
}
Is there something in T-SQL's BEGIN CATCH functionality that does the same?
You can use RAISERROR. From the MSDN documentation on RAISERROR:
BEGIN TRY
-- RAISERROR with severity 11-19 will cause execution to
-- jump to the CATCH block
RAISERROR ('Error raised in TRY block.', -- Message text.
16, -- Severity.
1 -- State.
);
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
-- Use RAISERROR inside the CATCH block to return
-- error information about the original error that
-- caused execution to jump to the CATCH block.
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH;
EDIT:
This is not really the same thing as c#'s throw or throw ex. As #henrikstaunpoulsen points out you don't get the original error number in the new error (RAISERROR is restricted in which numbers it can use). You would have to use some sort of convention and parse the information (if available) out of the message.
MSDN has an article Using TRY...CATCH in Transact-SQL and I used some of the code to create the test below:
use test;
GO
IF OBJECT_ID (N'usp_RethrowError',N'P') IS NOT NULL
DROP PROCEDURE usp_RethrowError;
GO
CREATE PROCEDURE usp_RethrowError AS
IF ERROR_NUMBER() IS NULL
RETURN;
DECLARE
#ErrorMessage NVARCHAR(4000),
#ErrorNumber INT,
#ErrorSeverity INT,
#ErrorState INT,
#ErrorLine INT,
#ErrorProcedure NVARCHAR(200);
SELECT
#ErrorNumber = ERROR_NUMBER(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorLine = ERROR_LINE(),
#ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
SELECT #ErrorMessage =
N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' +
'Message: '+ ERROR_MESSAGE();
RAISERROR
(
#ErrorMessage,
#ErrorSeverity,
#ErrorState,
#ErrorNumber, -- parameter: original error number.
#ErrorSeverity, -- parameter: original error severity.
#ErrorState, -- parameter: original error state.
#ErrorProcedure, -- parameter: original error procedure name.
#ErrorLine -- parameter: original error line number.
);
GO
PRINT 'No Catch'
DROP TABLE XXXX
PRINT 'Single Catch'
BEGIN TRY
DROP TABLE XXXX
END TRY
BEGIN CATCH
EXEC usp_RethrowError;
END CATCH;
PRINT 'Double Catch'
BEGIN TRY
BEGIN TRY
DROP TABLE XXXX
END TRY
BEGIN CATCH
EXEC usp_RethrowError;
END CATCH;
END TRY
BEGIN CATCH
EXEC usp_RethrowError;
END CATCH;
Which produces the following output:
No Catch
Msg 3701, Level 11, State 5, Line 3
Cannot drop the table 'XXXX', because it does not exist or you do not have permission.
Single Catch
Msg 50000, Level 11, State 5, Procedure usp_RethrowError, Line 25
Error 3701, Level 11, State 5, Procedure -, Line 7, Message: Cannot drop the table 'XXXX', because it does not exist or you do not have permission.
Double Catch
Msg 50000, Level 11, State 5, Procedure usp_RethrowError, Line 25
Error 50000, Level 11, State 5, Procedure usp_RethrowError, Line 25, Message: Error 3701, Level 11, State 5, Procedure -, Line 16, Message: Cannot drop the table 'XXXX', because it does not exist or you do not have permission.
In SQL 2012 they added the new THROW keyword, that can also be used to re-throw an exception
USE tempdb;
GO
CREATE TABLE dbo.TestRethrow
( ID INT PRIMARY KEY
);
BEGIN TRY
INSERT dbo.TestRethrow(ID) VALUES(1);
-- Force error 2627, Violation of PRIMARY KEY constraint to be raised.
INSERT dbo.TestRethrow(ID) VALUES(1);
END TRY
BEGIN CATCH
PRINT 'In catch block.';
THROW;
END CATCH;
http://msdn.microsoft.com/en-us/library/ee677615.aspx
Here is what I have used to rethrow an exception after rolling back the transaction. This gives the line number information of the error too.
BEGIN TRY
BEGIN TRANSACTION -- Start the transaction
-- Do your work here
-- Commit the transaction
COMMIT TRANSACTION
END TRY
BEGIN CATCH
-- There was an error, rollback the transaction
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
-- Raise an error with the details of the exception
DECLARE #ErrorMessage nvarchar(2048)
DECLARE #ErrorProcedure nvarchar(128)
DECLARE #ErrorState int
DECLARE #ErrorLine int
DECLARE #ErrorSeverity int
SET #ErrorProcedure = ERROR_PROCEDURE()
SET #ErrorLine = ERROR_LINE()
SET #ErrorSeverity = ERROR_SEVERITY()
SET #ErrorState = ERROR_STATE()
SET #ErrorMessage = ''
IF #ErrorProcedure IS NOT NULL
SET #ErrorMessage = #ErrorMessage + #ErrorProcedure + ' ';
IF #ErrorLine IS NOT NULL
SET #ErrorMessage = #ErrorMessage + '[Line ' + CAST(#ErrorLine as nvarchar) + '] ';
SET #ErrorMessage = #ErrorMessage + ERROR_MESSAGE()
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState)
END CATCH
In order to prevent the repetition of procedure information/error/line numbers in multiple catch scenarios, I use a similar procedure, with the slight modification as follows:
IF #Error_Procedure <> OBJECT_NAME(##PROCID)
BEGIN
RAISERROR('[Procedure: %s]: Nest Level: %d; Line: %d; Error Number: %d; Message: %s',#Error_Severity,#Error_State,#Error_Procedure, #NestLevel, #Error_Line, #Error_Number, #Error_Message)
END
ELSE
BEGIN
RAISERROR(#Error_Message,#Error_Severity,#Error_State)
END
So if we have already caught and re-raised the error with this SP, we don't repeatedly add the additional information, so at the outer scope, we see only the error as originally re-raised.
In the examples posted above, the double-catch output would be the same as the single-catch output. I also include the nest level in the error message to aid with debugging.
you can raise exceptions using RAISEERROR
http://msdn.microsoft.com/en-us/library/ms178592.aspx
I generally use the following:
DECLARE #Outcome as bit
DECLARE #Error as int
BEGIN TRANSACTION
-- *** YOUR TSQL TRY CODE HERE ****
-- Capture the TSQL outcome.
SET #Error = ##ERROR
-- Set the Outcome to be returned to the .NET code to successful
SET #Outcome = 1
IF #Error <> 0
BEGIN
-- An Error was generate so we invoke ROLLBACK
ROLLBACK
-- We set the Outcome to be returned to .Net to unsuccessful
SET #Outcome = 0
end
ELSE
BEGIN
-- The transaction was successful, invoke COMMIT
COMMIT
END
-- Returning a boolean value to the .NET code
Select #Outcome as Outcome