I wrote this piece of code in PL/PGSQL.
WHILE nbStatus1 < nbStatus0 AND StatusExec = 1 LOOP
BEGIN
SELECT Id, query INTO idQuery, Selectquery FROM temp_table_test WHERE Status = 0 LIMIT 1;
EXECUTE Selectquery;
UPDATE temp_table_test
SET Status = 1
WHERE Id = idQuery;
nbStatus1 := nbStatus1 + 1;
EXCEPTION
WHEN others THEN
UPDATE LOGS_TEST_DETAILS
SET ENDDATE = NOW(),
ERRORMESSAGE = SQLERRM,
ERRORCODE = SQLSTATE
WHERE TRAITEMENTID = lastTraitementId AND QUERYID = idQuery;
StatusExec := 0;
ROLLBACK;
END;
COMMIT;
END LOOP;
I use EXECUTE to CALL a list of procedure, one after the other in the while loop.
Each CALL is stored in the variable Selectquery.
It works fine but...
I'm trying to implement a kind of try catch in case i made a mistake in one of the procedure i call in the loop.
So i did a mistake (on purpose) inside one of the procedure i call. But the ROLLBACK, remove all the changes i did, even before the while loop. it kinda propagates to all my code
Is there a way to ROLLBACK only the changes that occured within the EXECUTE command and then exit the loop ?
There is something to deal with savepoint i guess, but i can't sort it out
Thanks for your help
Consider the following stored procedure
CREATE PROCEDURE AssignCodeToCustomer (#customerId int)
AS
BEGIN
DECLARE #code NVARCHAR(255)
BEGIN TRY
BEGIN TRANSACTION
SELECT #code = (
UPDATE
Codes
SET
CustomerId = #customerId
OUTPUT
INSERTED.Code
FROM (
SELECT TOP 1
Code
FROM
Codes
) AS c
WHERE
c.Code = Codes.Code
-- Other stuff
COMMIT TRANSACTION
END TRY
BEGIN CATCH
BEGIN
ROLLBACK TRANSACTION
EXEC spLogSQLError
END
END CATCH
END
GO
I get an error 'Incorrect syntax near the keyword UPDATE' on line 10 (which holds the keyword UPDATE). I could also first select a code and then assign it, but with concurrency in mind I want only one query. The query works if I don't try to set the output value into the variable. How can I fix this error or should I use another approach?
My application uses pl/pgsql functions that return integers.
The returned integer is used as a return code, to distinguish between different errors.
For example, if a function that insert datas returns -1, it means that some datas exist already and this is forbidden to try to insert the same data again. But if it returns -2, it means something else. That way the application knows the error and can display a useful error message to the user.
My problem now is that i want, at some points in the function, to return immediately when i detect an error, and rollback everything done so far in the function. If i use "raise exception", it will rollback, but not return an integer. If i use "return -1;", it will return an integer, but not rollback modifications. So i'm stuck, because obviously i can't do both
Here's a phony example function:
CREATE OR REPLACE FUNCTION create_flight_and_passengers(
_date timetamp,
_name text,
_passengers integer[]
)
RETURNS integer
LANGUAGE plpgsql
AS $$
DECLARE
return_code integer;
BEGIN
INSERT INTO flights
VALUES (_name);
SELECT function_1(_date, _passengers) into return_code;
if (return_code = -1) then
-- [1] rollback everything done since beginning of function
-- create_flight_and_passengers() and return -1
end if;
SELECT function_2(_date, _passengers) into return_code;
if (return_code = -1) then
-- [2] rollback everything done since beginning of function
-- create_flight_and_passengers() and return -2
end if;
return 0;
END;
$$;
In [1] and [2] i could use raise exception to rollback, but when i do this i don't have a returned integer.
I could set a local variable in [1] and [2], then raise exception, and in EXCEPTION do tests on this variable to know where the exception come from, but this is bloated, there must be something better !
I'm not even sure you can rollback effects of a function you have called and that has terminated (function_1() and function_2() in my example)
Any ideas ?
This is a pretty bizarre thing to want to do. If you really need to, you could do it like this:
DECLARE
retval integer;
BEGIN
retval := 0;
BEGIN
... make my changes ...
IF (... is something wrong? ...) THEN
RAISE EXCEPTION SQLSTATE '0U001';
END IF;
EXCEPTION
WHEN '0U001' THEN
retval := -1;
END;
END;
The concept here is that a BEGIN ... EXCEPTION block defines a subtransaction. A RAISE EXCEPTION within the block rolls back the subtransaction. We catch it at the outer level, preventing the exception from propagating outside the function and aborting the whole transaction.
See the PL/PgSQL documentation.
I have some SQL:
BEGIN TRY
DECLARE #RowsInserted int;
SET #RowsInserted = ##ROWCOUNT;
SELECT #RowsInserted+'test' as [SUCCESS];
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE() AS [ERROR]
END CATCH
To my surprise, this actually produces two batches (data sets):
I can't use GO to split the batches due to the way TRY...CATCH works. So does that mean there's always some dummy result set if there's some error caught?
What I'd really like to do is throw away the SUCCESS batch (or other dummy batches like this when caught). Otherwise I'd have to navigate through some garbage batches to find the Error reporting batch in the catch statement which seems confusing.
What you need to do in the catch block is raise an error rather than selecting it.
BEGIN CATCH
-- rethrow error
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
DECLARE #ErrorSeverity INT = ERROR_SEVERITY()
DECLARE #ErrorState INT = ERROR_STATE()
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState)
END CATCH
There's a separation there: result sets (select) are ment for data, while errors (raiserror) are ment to communicate failures.
I don't think it's possible - the reference to the resultset is returned to the client before the enumeration begins and before it's possible to know that any record evaluation will produce an error.
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