Try and catch not working for stored procedure - sql

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.

Related

Stored procedure best practice? Should they check if foreign keys exist before inserting?

When building a stored procedure that inserts into a simple link table, should it check if the FK's exist and gracefully return an error or just let SQL throw an exception?
What is best practice?
What is most efficient?
Just in case someone doesn't understand my question:
Table A
Table B
Table AB
Should I do:
IF EXISTS(SELECT 1 FROM A WHERE Id = #A) AND EXISTS(SELECT 1 FROM B WHERE Id = #B)
BEGIN
INSERT AB (AId,BId) VALUES (#A, #B)
END
ELSE
--handle gracefully, return error code or something
or
INSERT AB (AId,BId) VALUES (#A, #B)
and let SQL throw an exception
Thanks
If the tables are under your control, there is no reason to perform an extra check. Just assume they are set up correctly, and let SQL handle any error. Constantly checking that you have indeed done what you intended to do is overly defensive programming that adds unnecessary complexity to your code.
For example, you wouldn't write code like this:
i = 1;
if (i != 1)
{
print "Error: i is not 1!";
}
And I view this situation as similar.
If the tables are not under your control it may be useful to handle the error gracefully. For example, if this procedure can run on an arbitrary set of tables created by the user, or if it will be distributed to external users who are required to set up the tables in their own database, you may want to add some custom error handling. The purpose of this would be to give the user a clearer description of what went wrong.
As a basic concept, validating values before a potentially error raising code is a good thing. However, in this case, there could be (at least theoretically) a change in table a or table b between the exists checks and the insert statement, that would raise the fk violation error.
I would do something like this:
BEGIN TRY
INSERT AB (AId,BId) VALUES (#A, #B)
SELECT NULL As ErrorMessage
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE() AS ErrorMessage
END CATCH
The ERROR_MESSAGE() function returns the error that was rasied in the try block.
Then in the executing code you can simply check if the returned error message is null. If it is, you know the insert was successful. If not, you can handle this exception how ever you see fit.
Usually i will check Fk. If the Fk isn't exist, then throw a error friendly, and the insert statement will not be execute, database will not lock the table too.

SQL Server TRY CATCH FINALLY

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

Ole db command & transactions. General Error

I have an OLE DB Command that originally contained a simple three line update:
update raw_CommissionPaid set PolicyNumber = ? where PolicyNumber = ?
and this worked fine, did exactly as expected and all was good.
I recently decided though that this has the potential to go wrong and do a lot of damage so i decided i would put it all in a transaction, monitor the rows affected and roll back if it changed more than what i was expecting, so i changed my update to this:
BEGIN TRY BEGIN TRAN UpdateCommissionsPaidPolNo
update raw_CommissionPaid set PolicyNumber = ? where PolicyNumber = ?
IF ##ROWCOUNT <> 1 RAISERROR('Row count <> 1', 11, 1)
COMMIT TRAN END TRY BEGIN CATCH
ROLLBACK TRAN UpdateCommissionsPaidPolNo
PRINT 'UpdateCommissionsPaidPolicyNumber script failed'
SELECT
ERROR_MESSAGE() as ErrorMessage END CATCH
However this gave me the "Syntax error or general error" message that it usually gives. I remembered from a previous issue that sometimes it cant map parameters to the ?'s if they are embedded within other SQL, i thought that might be the issue so changed it to this just incase:
Declare #FNumber varchar(20) declare #LNumber varchar(20)
set #FNumber = ? set #LNumber = ?
BEGIN TRY BEGIN TRAN UpdateCommissionsPaidPolNo
update raw_CommissionPaid set PolicyNumber = #FNumber where
PolicyNumber = #LNumber
IF ##ROWCOUNT <> 1 RAISERROR('Row count <> 1', 11, 1)
COMMIT TRAN END TRY BEGIN CATCH
ROLLBACK TRAN UpdateCommissionsPaidPolNo
PRINT 'UpdateCommissionsPaidPolicyNumber script failed'
SELECT
ERROR_MESSAGE() as ErrorMessage END CATCH
but from this i still get :
Syntax Error, permission violation or other non specific error
i realised it might be because of the print, or the returning of the error message, but removing them doesnt change anything, it still fails
im sure the sql is valid as i tested it in SQL server management studio.
Anyone faced this? Is there a setting im supposed to change to allow this kind of change?
Thanks in advance
After reading This site i learned that if i set the data flow tasks TransactionOption property to supported then if it fails it supposedly rolls the failing section back itself.
Armed with this knowledge i figured then the only thing i needed to do was make it realise that multiple rows being updated counted as an error. So changing it to just this:
update raw_CommissionPaid set PolicyNumber = ? where PolicyNumber = ?
IF ##ROWCOUNT <> 1 RAISERROR('Row count <> 1', 11, 1)
should make it roll itself back.

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.

TRY CATCH Block in T-SQL

I encountered a stored procedure that had the following error handling block immediately after an update attempt. The following were the last lines of the SP.
Is there any benefit of doing this? It appears to me as though this code is just rethrowing the same error that it caught without any value added and that the code would presumably behave 100% the same if the Try Block were ommited entirely.
Would there be ANY difference in the behavior of the resulting SP if the TRY block were ommitted?
BEGIN CATCH
SELECT #ErrMsg = ERROR_MESSAGE(), #ErrSev = ERROR_SEVERITY(), #ErrState = ERROR_STATE()
RAISERROR (#ErrMsg, #ErrSev, #ErrState)
END CATCH
Barring the fact that the "line error occured on" part of any message returned would reference the RAISERROR line and not the line the error actually occured on, there will be no difference. The main reason to do this is as #Chris says, to allow you to programmatically use/manipulate the error data.
What we usually do in our stored procedure is to write the catch block like this
BEGIN CATCH
DECLARE #i_intErrorNo int
DECLARE #i_strErrorMsg nvarchar(1000)
DECLARE #i_strErrorProc nvarchar(1000)
DECLARE #i_intErrorLine int
SELECT #i_intErrorNo=Error_Number()
SELECT #i_strErrorMsg=Error_Message()
SELECT #i_strErrorProc=Error_Procedure()
SELECT #i_intErrorLine=Error_Line()
INSERT INTO error table ////// Insert statement.
END CATCH
This is something we use to do to store error. For proper message to user, I always use the output parameter to the stored procedure to show the detailed/required reason of the error.
if you look on the msdn page for RAISERROR then you see this general description:
Generates an error message and
initiates error processing for the
session. RAISERROR can either
reference a user-defined message
stored in the sys.messages catalog
view or build a message dynamically.
The message is returned as a server
error message to the calling
application or to an associated CATCH
block of a TRY…CATCH construct.
It appears that the "calling application" will get the error message. It may be that the creator of the stored procedure wanted only the error message, severity, and state to be reported and no other options that can be added. This may be because of security concerns or just that the calling application did not need to know the extra information (which could have been verbose or overly detailed, perhaps).
There is a subtle difference, as demonstrated below.
First setup the following:
CREATE TABLE TMP
( ROW_ID int NOT NULL,
ALTER TABLE TMP ADD CONSTRAINT PK_TMP PRIMARY KEY CLUSTERED (ROW_ID)
)
GO
CREATE PROC pTMP1
AS
BEGIN TRY
INSERT INTO TMP VALUES(1)
INSERT INTO TMP VALUES(1)
INSERT INTO TMP VALUES(2)
END TRY
BEGIN CATCH
DECLARE #ErrMsg varchar(max)= ERROR_MESSAGE(),
#ErrSev int = ERROR_SEVERITY(),
#ErrState int = ERROR_STATE()
RAISERROR (#ErrMsg, #ErrSev, #ErrState)
END CATCH
GO
CREATE PROC pTMP2
AS
INSERT INTO TMP VALUES(1)
INSERT INTO TMP VALUES(1)
INSERT INTO TMP VALUES(2)
GO
Now run the following:
SET NOCOUNT ON
DELETE TMP
exec pTMP1
SELECT * FROM TMP
DELETE TMP
exec pTMP2
SELECT * FROM TMP
SET NOCOUNT OFF
--Cleanup
DROP PROCEDURE pTMP1
DROP PROCEDURE pTMP2
DROP TABLE TMP
You should get the following results:
Msg 50000, Level 14, State 1, Procedure pTMP1, Line 12
Violation of PRIMARY KEY constraint 'PK_TMP'. Cannot insert duplicate key in object 'dbo.TMP'. The duplicate key value is (1).
ROW_ID
-----------
1
Msg 2627, Level 14, State 1, Procedure pTMP2, Line 4
Violation of PRIMARY KEY constraint 'PK_TMP'. Cannot insert duplicate key in object 'dbo.TMP'. The duplicate key value is (1).
The statement has been terminated.
ROW_ID
-----------
1
2
Notice that the TRY..CATCH version did not execute the third INSERT statement, whereas the pTMP2 proc did. This is because control jumps to CATCH as soon as the error occurs.
NOTE: The behaviour of pTMP2 is affected by the XACT_ABORT setting.
Conclusion
The benefit of using TRY..CATCH as demonstrated depends on how you manage your transaction boundaries.
If you roll-back on any error, then the changes will be undone. But this doesn't eliminate side-effects such as addtional processing. NOTE: If a different session simultaneously queries TMP using WITH(NOLOCK) it may even be able to observe the temporary change.
However, if you don't intend rolling back a transaction, you may find the technique is quite important to prevent certain data changes being applied in spite of an earlier error.