I have a stored procedure that seems not to be logging its errors correctly.
The code is erroring, but the catch block doesn't seem to be coming into effect.
The try block is fairly long - but the erroring section is simple and comes rightat the end, so I've precis'd that.
BEGIN TRY
insert into tbl_X
select * from #temp_tbl_Y
RETURN 1
END TRY
BEGIN CATCH
Insert Into ExtractsErrorLog
SELECT
getdate() as ErrorDate
,object_name(##procid) as ProcedureName
,ERROR_NUMBER() as ErrorNumber
,ERROR_LINE() as ErrorLine
,ERROR_MESSAGE() as ErrorMessage
;
DECLARE #errormessage as varchar(max);
DECLARE #errorseverity as int;
DECLARE #errorstate as int;
set #errormessage = ERROR_MESSAGE();
set #errorseverity = ERROR_SEVERITY();
set #errorstate = ERROR_STATE();
RAISERROR (#errormessage,
#errorseverity,
#errorstate
);
END CATCH;
The error the proc is failing on is our old friend
"Column name or number of supplied values does not match table definition."
I've fixed that error - It was a dumb lazy mistake - but I'm baffled why my error logging process didn't seem to be working - no row is being inserted into my ExtractsErrorLog table.
TSQL's TRY...CATCH does not catch that error. This error falls into the "compilation/recompilation" type errors that are not handled by the CATCH block "within the same level of execution".
From MSDN:
The following types of errors are not handled by a CATCH block when
they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from
running.
Errors that occur during statement-level recompilation, such
as object name resolution errors that occur after compilation because
of deferred name resolution
...
You can use TRY…CATCH to handle errors that occur during compilation
or statement-level recompilation by executing the error-generating
code in a separate batch within the TRY block. For example, you do
this by placing the code in a stored procedure or by executing a
dynamic Transact-SQL statement using sp_executesql. This allows
TRY…CATCH to catch the error at a higher level of execution than the
error occurrence. For example, the following code shows a stored
procedure that generates an object name resolution error. The batch
that contains the TRY…CATCH construct is executing at a higher level
than the stored procedure; and the error, which occurs at a lower
level, is caught.
I ran into similar issues with a script creating a transaction inside a TRY...CATCH that would ROLLBACK the transaction if it failed. A statement inside the transaction was throwing that same error and caused the transaction to never be closed, as the CATCH was never entered.
As mentioned in the MSDN article, one alternative is to create a stored procedure out of your INSERT statement and then call that inside your try/catch. If the sproc is wrong, you'll catch the compilation error while trying to create it. If the table definition later changes to invalidate the sproc, then the TRY...CATCH will catch the exception for you.
If you want it to all live in one script, you can make it a temporary stored procedure, but you will then need to handle the compilation errors while you are creating the sprocs. It's not pretty, but it will work:
-- Creating error sproc to re-use code
CREATE PROCEDURE #HandleError AS
Insert Into ExtractsErrorLog
SELECT GETDATE() as ErrorDate
,object_name(##procid) as ProcedureName
,ERROR_NUMBER() as ErrorNumber
,ERROR_LINE() as ErrorLine
,ERROR_MESSAGE() as ErrorMessage;
DECLARE #errormessage as varchar(max);
DECLARE #errorseverity as int;
DECLARE #errorstate as int;
set #errormessage = ERROR_MESSAGE();
set #errorseverity = ERROR_SEVERITY();
set #errorstate = ERROR_STATE();
RAISERROR ( #errormessage,
#errorseverity,
#errorstate);
GO
-- Create a stored procedure of our INSERT and catch any compilation errors
CREATE PROCEDURE #TEST AS
insert into tbl_X
select * from #temp_tbl_Y
GO
IF (##ERROR <> 0) BEGIN
exec #HandleError
-- If there was an error creating the sprocs, don't continue to the next batch
RETURN
END
-- If compilation succeeded, then run the sproc
BEGIN TRY
exec #TEST
RETURN
END TRY
BEGIN CATCH
exec #HandleError
END CATCH;
I used THROW in my CATCH block before the INSERT statement for my logging - and got the same issue as you. Once I moved THROW after the logging INSERT statement it worked. Looks like THROW might terminate the session.
You don't use THROW in your code example, but thought this might help someone else.
It's your RETURN: "Exits unconditionally from a query or procedure. RETURN is immediate and complete and can be used at any point to exit from a procedure, batch, or statement block."
Related
I am new to T-SQL programming. I need to write a main procedures to execute multiple transactions. How could i structure the program so that each transaction will not abort. Instead, the procedure will raise the error and report them back to the main program in the output parameters after all the transaction finish running. Please provide me with pseudo code if you can. Thanks.
You need to follow the template from Exception handling and nested transactions
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(), #message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end
go
As you can see you can't always continue, because sometime the exception has already aborted the transaction by the time you catch it (the typical example being deadlock exception 1205). And you must use a savepoint and revert to the savepoint in case of exception, to keep the database consistent. However, you do not abort the caller's work, if possible.
You could use try/catch
BOL - TRY/CATCH
Here's an example
I have previously encapsulated logic into stored procedures and put in exec statements in the TRY/CATCH block. In the CATCH you can use this link to get error information (example B in the link)
BOL - ERROR_MESSAGE
Something similar to -
BEGIN TRY
BEGIN TRAN
EXEC StoredProcedure01
EXEC StoredProcedure02
COMMIT
END TRY
BEGIN CATCH
ROLLBACK TRAN
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO
I might consider trying this. Each transaction is a separate stored proc with a an overall stored proc that calls each in turn. Save the error information you want in a table variable. IN the catch block of each proc, rollback that transaction and then insert the data form the table variable with the error information into a logging table. Do not return a failure to the calling proc.
If you want to report the errors out of the main proc in real time, you can do a select from the logging table at the end.
It would work best if you create a batchid at the start of the calling proc and have that be an input variable to each of the procs you call and also include that data in the information you add to to the logging table. Then if the procs fail multiple times during nonworking hours, you have the errors for all of them and can see the batch they were associated with. This helps tremendously in tracking down problems.
You will need to give some thought as to what information you will want for each proc when designing your logging table. I would suggest that part of what you store is any input variables that are sent into the proc. Also if you are using dynamic SQl, then store the generated sql as well. If you can identify the user who ran the proc, that too is useful for tracking down permissions issues for instance.
Having a logging table is far more useful than just returning errors at run time. You can look for trends, see if the same error is frequently happening, look at the information that caused failures and the information that succeeded if you choose to also log the variables for successful runs.
None of this is out of the box easy to write code. It requires a great deal of design on your part to determine, exactly what information will be useful over time in troubleshooting issues with the process. How detailed you need to get is a business decision based on both how the data is used and what you will want to know about a failure. As such, we cannot make this determination for you.
I want to call my ms sql stored procedure from nservicebus handler and I wonder if I still need to use something like
CREATE PROCEDURE [dbo].[proc_fd_SomeEntitySyncRawWithStage]
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
-- Batch of inserts and updates that I want to keep transactional
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage,
#ErrorSeverity,
#ErrorState
);
END CATCH;
END
Do I really need all this TRY BEGIN TRAN... CATCH ROLLBACK .. or SET XACT_ABORT ON... stuff to rollback my transaction or NServiceBus will rollback it in case of any sql exception in message handler ?
P.S.
Keeping in mind that:
"If a run-time statement error (such as a constraint violation) occurs in a batch, the default behavior in the Database Engine is to roll back only the statement that generated the error"
and
"When SET XACT_ABORT is OFF, in some cases only the Transact-SQL statement that raised the error is rolled back and the transaction continues processing."
Normally, you should always wrap your procedure calls in a transaction, would you use ORM or ADO.NET. Wht ADO.NET you would do something like
var ts = myConnection.BeginTransaction();
and then call your procedure within a try-catch block.
Further depends on your logic. If you procedure could fail because of some logic, you will need to have a return code and throw an exception based on that. If your procedure just fails with SQL exception, the try-catch block will handle it.
In the catch block you need to rollback the transaction.
If you re-throw your exception in the catch block, the message will be retried by FLR and if configured - by SLR. Usually, this helps when having deadlicks in the database, FLR normally handles this.
I have a stored procedure spMyProc. The procedure is passed a table name as a parameter #TableName. It is important to verify that the table exists before proceeding to the body of the procedure.
How can I create a custom exception to raise an error if the table name is invalid?
I am aware of TRY and CATCH but I'm unsure how to put this together with custom exceptions.
Regards.
Look into RAISERROR() funiction.
TRY.. CATCH works pretty same as it would work in any other programming language
BEGIN TRY
-- do your checks
IF NOT EXISTS(SELECT 1 FROM sys.tables WHERE NAME = #TableName)
BEGIN
RAISERROR('Table does not exist', 16,1)
END
-- rest of the code if checks are passed
-- if above checks are not passed and you riase an error
-- control will skip any code in TRY Block after the error has been
-- Raised and staright jump to Catch block.
END TRY
BEGIN CATCH
-- Do your error logging
-- Other stuff
-- you have access to ERROR_ functions here to get detailed info about errors
END CATCH
I think one of examples supplied here RAISERROR should match your problem. New versions of SQL Server will use THROW instead of RAISERROR.
You can use try catch and raiseerror for handle the custom exception, for example:
begin try
--sql statements
end try
begin catch
RAISERROR
(
ERROR_MESSAGE(), -- or add your custom message
ERROR_SEVERITY(),
1,
ERROR_NUMBER(), -- parameter: original error number.
ERROR_SEVERITY(), -- parameter: original error severity.
ERROR_STATE(), -- parameter: original error state.
ISNULL(ERROR_PROCEDURE(), '-'), -- parameter: original error procedure name.
ERROR_LINE() -- parameter: original error line number.
end catch
I would like to log warnings thrown from my Transact SQL scripts that aren't going to get caught in a TRY...CATCH block. Is there any way to do this? ERROR_NUMBER(), etc. won't work outside of a catch block and I'm unsure of how to even know to know a warning was thrown. Googling hasn't yielded much.
The documentation seems to intend that the error message be passed pack to the caller. It does also however state that if you wrap the statements in a stored procedure, and then call that one within a try-catch block, you will catch low-severity errors.
-- Verify that the stored procedure does not exist.
IF OBJECT_ID ( N'usp_ExampleProc', N'P' ) IS NOT NULL
DROP PROCEDURE usp_ExampleProc;
GO
-- Create a stored procedure that will cause an
-- object resolution error.
CREATE PROCEDURE usp_ExampleProc
AS
SELECT * FROM NonexistentTable;
GO
BEGIN TRY
EXECUTE usp_ExampleProc;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
You cannot catch these errors with a try catch even if you wrap it in a proc and try. Here is an example.
CREATE PROC P
AS
BEGIN
RAISERROR('TEST',9,-1,-1)
END;
BEGIN TRY
EXEC P
END TRY
BEGIN CATCH
PRINT 'CAUGHT'
END CATCH;
I've got a job scheduled through the SQL Server Agent that runs a sproc which runs some other sprocs. Every sproc looks like this:
BEGIN TRY
-- do stuff
END TRY
BEGIN CATCH
DECLARE #errorMessage varchar(4000)
DECLARE #procName varchar(255)
SELECT #errorMessage = error_message()
SELECT #procName = OBJECT_NAME(##PROCID)
RAISERROR('%s threw an exception: %s', 16, 1, #procName, #errorMessage)
END CATCH
This all works fine - errors are raised and thrown up the stack, life is good. However, my RAISERROR calls don't appear to cause the job to fail - I'm set to receive an e-mail notification "When the job fails," but never receive one. E-mail notifications are working, as I will get emails if I change the notification to "when the job succeeds". Is there some other function I should be using here in place of RAISERROR?
Raise an error in the try block with severity between 11-19 in TRY block and then re-raise the same error in catch block. This will make the step fail..
code snippet from msdn
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;
Each job step has an action for step failure, they must set to fail the entire job. Yours is probably set to go to next step only.
If your error severity level is 20 or higher, the database connection will be terminated. This will cause the step to fail. So long as your step is set so that the job fails if the step fails, you'll get your desired outcome.
RAISERROR('%s threw an exception: %s', 20, 1, #procName, #errorMessage) WITH LOG;
SEE msdn description of RAISERROR: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/raiserror-transact-sql