SQL Server TRY CATCH FINALLY - sql

I have a scenario where I need something similar to .NET's try-catch-finally block.
On my try, I will CREATE a #temp table, INSERT data to it & process other data sets based on #temp.
On CATCH then RAISERROR.
Is it possible to have a FINALLY block to DROP #temp?
Below is the pseudo code:
BEGIN TRY
CREATE TABLE #temp
(
--columns
)
--Process data with other data sets
END TRY
BEGIN CATCH
EXECUTE usp_getErrorMessage
END CATCH
BEGIN FINALLY
DROP TABLE #temp
END FINALLY

While not exactly the same as FINALLY, the T-SQL version of Try-Catch does allow that code that needs execute after both the Try and Catch blocks can occur after the end of the END CATCH statement.
Using the question code as an example:
BEGIN TRY
CREATE TABLE #temp
(
--columns
)
--Process data with other data sets
END TRY
BEGIN CATCH
EXECUTE usp_getErrorMessage
END CATCH;
IF OBJECT_ID('tempdb..#temp') IS NOT NULL -- Check for table existence
DROP TABLE #temp;
The DROP TABLE command will execute whether the Try or Catch execute.
See: BOL Try...Catch

Instead of creating a table you could just declare a table variable (which will automatically go away when the query ends).
BEGIN TRY
DECLARE #temp TABLE
(
--columns
)
--do stuff
END TRY
BEGIN CATCH
--do other stuff
END CATCH

there is no FINALLY equivalent.
an alternative may be table variables but is not exactly the same and must be evaluated on a case by case basis. there is a SO question with details very useful to make an informed choice.
with table variables you don't need to clean up like you do with temp tables

"FINALLY" is often, but not always, functionally identical to having the "final" code follow the TRY/CATCH (without a formal "FINALLY" block). Where it is different is the case where something in the TRY/CATCH blocks could cause execution to end, such as a return statement.
For example, a pattern I've used is to open a cursor, then have the cursor-using code in the TRY block, with the cursor close/deallocate following the TRY/CATCH block. This works fine if the blocks won't exit the code being executed. However, if the TRY CATCH block does, for example, a RETURN (which sounds like a bad idea), if there were a FINALLY block, it would get executed, but with the "final" code placed after the TRY / CATCH, as T-SQL requires, should those code blocks cause the execution to end, that final code won't be called, potentially leaving an inconsistent state.
So, while very often you can just put the code after the TRY/CATCH, it will be a problem if anything in those blocks could terminate without falling through to the cleanup code.

Local temp tables (e.g., "#Temp") are automatically dropped when the SQL connection ends. It's good practice to include an explicit DROP command anyway, but if it doesn't execute, the table will still be dropped.
If you must ensure that a DROP executes as soon as possible, you'll have to repeat the DROP command in a CATCH clause, since there's no FINALLY:
-- create temp table;
BEGIN TRY
-- use temp table;
-- drop temp table;
END TRY
BEGIN CATCH
-- drop temp table;
THROW; -- rethrow the error
END CATCH
Table variables are an alternative: they're dropped when the variable goes out of scope. However, table variables do not support statistics, so if the table variable is large and used in multiple queries, it may not perform as well as a temp table.

using custom error number to indicate there no real error, just final code?
-- create temp table;
BEGIN TRY
-- use temp table;
THROW 50555;
END TRY
BEGIN CATCH
-- drop temp table;
IF ERROR_NUMBER() <> 50555
THROW; -- rethrow the error
END CATCH

The correct answer in this case is the one proposed by #Dave Bennett; after the TRY/CATCH block check for the existence of the table and drop it.
But what if you are raising an exception out of your CATCH and you need to do some "FINALLY" type processing?
Could it be as simple as setting a variable in the CATCH and checking it after you fall out of the CATCH?
DECLARE #is_error BIT = 0;
BEGIN TRY
--Process data with other data sets
END TRY
BEGIN CATCH
-- Your exception handling code here
SET #is_error = 1;
END CATCH
-- Your "FINALLY" code here.
-- Then Check if you need to RAISERROR
IF #is_error = 0
BEGIN
-- Your success code
END
ELSE
BEGIN
-- Your fail code
-- RAISERROR
END;

With T-SQL, code placed after the TRY-CATCH block will get executed. So the answer to the question is simply to add a DROP TABLE IF EXISTS #temp right after the END CATCH like so:
BEGIN TRY
CREATE TABLE #temp(
--columns
)
--Process data with other data sets
END TRY
BEGIN CATCH
EXECUTE usp_getErrorMessage
END CATCH
-- Anything under this line will execute regardless of if the code reached the CATCH block or not.
DROP TABLE IF EXISTS #temp; -- Works in SQL Server 2016+, for older versions see here: https://stackoverflow.com/a/31171650/15596537

Related

Try and catch not working for stored procedure

I'm creating a procedure to delete a row from my plants Table. The try and catch statement was to delete it but if the plantID doesn't match any plant, catch the error and print the error message.
However, the catch isn't working and I'm unsure what how to fix it.
CREATE PROC spDeletePlant
#PlantID INT,
#PlantCode INT,
#PlantName VARCHAR(25),
#Description VARCHAR(50),
#ListPrice MONEY,
#BatchID INT,
#CategoryID INT
AS
BEGIN TRY
DELETE FROM Plants
WHERE PlantID = #PlantID
END TRY
BEGIN CATCH
THROW 50001, 'PlantID is incorrect.', 1
END CATCH;
Forgive the formatting, I am new to this site.
A query not matching any rows is not an error - when you run the delete statement on its own, you don't see an error raised.
If you want to automatically throw an error if no rows are deleted you can simply do:
DELETE FROM Plants
WHERE PlantID = #PlantID
IF ##Rowcount = 0
BEGIN
THROW 50001, 'PlantID is incorrect.', 1
END
Your code is working but is not doing what you expect. This is because delete statement doesn't not throw any error in case row is not found.
What you can do is :
Do a previous select to look for the register being deleted, otherwise throw an error. Look #Stu answer..
Check the affected rows variable. Which DB are you using? In MSSQL for example you can check the ##ROWCOUNT
PD:
Are you sure you want to throw an exception when a row is not found?
Usually Exceptions are reserved for an exceptional things such as I/O errors, bad params etc..
Additionally, you should take in a count that Exceptions costly so is not a good way of returning data.
I think you should check the number of affected rows.

use of GOTO & Labels in SQL inside SQL Transactions

I have a long running script that I want to wrap in transactions which could have up to 100+ transactions in it.
I would like to implement some error handling and re-use of code. I was thinking of using a label and GOTO statements.
BEGIN TRY
BEGIN TRANSACTION abc
--DO SOMETHING
COMMIT TRANSACTION abc;
END TRY
BEGIN CATCH
GOTO ERROR_OUT
END CATCH
ERROR_OUT:
SELECT ERROR_MESSAGE() [ErrorMessage];
ROLLBACK;
Is this something that should be done while using many transactions?
In my simple tests where I forced an error I noticed that the ERROR_MESSGE() SELECT statement did not return results.
Is there a way to get that to select statement to return results when using a label?
Declare #Table table (Num float,Den float,Pct float)
Insert into #Table values (25,100,0),(50,0,0)
BEGIN TRY
BEGIN TRANSACTION abc
-- Force a Divide by Zero on 2nd record
Update #Table Set Pct=Num/Den
COMMIT TRANSACTION abc;
Select * from #Table
GOTO CLEAN_OUT
END TRY
BEGIN CATCH
Declare #Error varchar(max)=Error_Message()
GOTO ERROR_OUT
END CATCH
ERROR_OUT:
ROLLBACK;
Select ErrorMessage=#Error
Select * From #Table
CLEAN_OUT:
Go
Returns
ErrorMessage
Divide by zero error encountered.
Num Den Pct
25 100 0
50 0 0
And then try it with (50,125,0)
In general, programmers are discouraged from using goto. The reason is that it is a control-flow primitive, and alternatives exist for many commonly used cases: if/then/else, while, and so on.
However, there are circumstances where goto is quite useful. And error handling is one of them. If you google "goto error handling", it is not hard to find explanations such as this, or this (although for C, I think the reasoning is very similar).
I have often used goto in SQL Server stored procedures for exactly this purpose. Although in my case, the goal was to achieve proper auditing and error processing when leaving the stored procedure.
Your use-case would seem to be a fine example where goto is a very reasonable way to code the block. I do find the "hundreds of transactions" part more questionable. Personally, I like to be explicit about when and where transactions are processed, so I would be more likely to write:
. . .
begin catch:
rollback ;
goto error_out;
end;
That is, to explicitly rollback the transaction "next to" where the transaction begins, rather than doing that in some far-away code block.

Try Catch Can't handle alter table

Why I this can't handle the alter table?
Begin Try
alter table nyork add [Qtr] varchar(20)
End Try
Begin Catch
Print 'Column already exist'
End Catch'
Because one of them is a transact sql command (the try catch) and the other is a DDL statement.
You'd probably do better off querying to see if the column exists before doing the alter statement.
To do this with MSSQL, see How to check if a column exists in a SQL Server table?
Specifically for your case,
IF COL_LENGTH('nyork', 'Qtr') IS NULL
BEGIN
alter table nyork
add [Qtr] varchar(20)
END
You cannot do such a thing. TRY...CATCH can only handle runtime errors. Your script will run as long as the column does not exist but not when it is already there. The name resolution of the objects is done at compile time. Therefore SQL Server will always recognize the missing column before it starts any execution. For that reason, you can't also do such a thing with dynamic SQL.
You can wrap it with exec('alter goes here'). Then catch will catch
As #Marcus Vinicius Pompeu said: "You'd probably do better off querying to see if the column exists before doing the alter statement."
But, if you really want to use TRY...CATCH with DDL. There is two way for do this.
Use dynamic SQL in the TRY block - It has been answered here.
Use stored procedure in the TRY block - Based on documentation.
Example based on your code:
DROP PROCEDURE IF EXISTS dbo.sp_my_proc
GO
CREATE PROCEDURE dbo.sp_my_proc
AS
--Your original code here:
ALTER TABLE nyork ADD [Qtr] VARCHAR(20)
GO
BEGIN TRY
EXECUTE dbo.sp_my_proc
--Optional
DROP PROCEDURE IF EXISTS dbo.sp_my_proc
END TRY
BEGIN CATCH
--Catch your error here
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH;

DDL exception caught on table but not on column

Assuming that the table MyTable already exists, Why does the "In catch" is printed on the first statement, but not on the second?
It seems to be catching errors on duplicate table names but not on duplicate column names
First:
BEGIN TRY
BEGIN TRANSACTION
CREATE TABLE MyTable (id INT)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'in Catch'
ROLLBACK TRANSACTION
END CATCH
Second:
BEGIN TRY
BEGIN TRANSACTION
ALTER TABLE MyTable ADD id INT
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'in Catch'
ROLLBACK TRANSACTION
END CATCH
The difference is that the alter table statement generates a compile time error, not a runtime error, so the catch block is never executed as the batch itself is not executed.
You can check this by using the display estimated execution plan button in SQL server management studio, you will see for the CREATE TABLE statement, an estimated plan is displayed, whereas for the ALTER TABLE statement, the error is thrown before SQL server can even generate a plan as it cannot compile the batch.
EDIT - EXPLANATION:
This is to do with the way deferred name resolution works in SQL server, if you are creating an object, SQL server does not check that the object already exists until runtime. However if you reference columns in an object that does exist, the columns etc that you reference must be correct or the statement will fail to compile.
An example of this is with stored procedures, say you have the following table:
create table t1
(
id int
)
then you create a stored procedure like this:
create procedure p1
as
begin
select * from t2
end
It will work as deferred name resolution does not require the object to exist when the procedure is created, but it will fail if it is executed
If, however, you create the procedure like this:
create procedure p2
as
begin
select id2 from t1
end
The procedure will fail to be created as you have referenced an object that does exist, so deferred name resolution rules no longer apply.

Why does the Try/Catch not complete in SSMS query window?

This sample script is supposed to create two tables and insert a row into each of them.
If all goes well, we should see OK and have two tables with data. If not, we should see FAILED and have no tables at all.
Running this in a query window displays an error for the second insert (as it should), but does not display either a success or failed message. The window just sits waiting for a manual rollback. ??? What am I missing in either the transactioning or the try/catch?
begin try
begin transaction
create table wpt1 (id1 int, junk1 varchar(20))
create table wpt2 (id2 int, junk2 varchar(20))
insert into wpt1 select 1,'blah'
insert into wpt2 select 2,'fred',0 -- <<< deliberate error on this line
commit transaction
print 'OK'
end try
begin catch
rollback transaction
print 'FAILED'
end catch
The problem is that your error is of a high severity, and is a type that breaks the connection immediately. TRY-CATCH can handle softer errors, but it does not catch all errors.
Look for - What Errors Are Not Trapped by a TRY/CATCH Block:
It looks like after the table is created, the following inserts are parsed (recompiled), which trigger statement level recompilations and breaks the batch.