I encountered a problem using T-SQL / SQL server (2017/140):
In my scenario there are a number of stored procedures (= worker procedures), which in turn perform different tasks and at the end output the result with a select statement.
A superordinate procedure (= runner procedure) calls the worker procedures one after the other and collects their result in a table using an insert-into statement.
Processing within the worker procedures is secured with try-/catch statements so that the runner procedure is not aborted if one of the worker procedures should fail. It is also ensured that the worker procedures always return a result, even if this procedure encounters an error during processing.
However, the runner procedure encounters an error if an error has occurred in a worker procedure, although this error has already been intercepted and handled in the worker procedure. The error message is: „The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.“ As you can see from my example script, no explicit transactions are used there.
The problem only occurs when the results of the worker procedures are transferred to a table with a Select-Into statement. As an example I have created another runner procedure, which only executes the worker procedures, but does not transfer their results to a table. In this case there will be no errors.
CREATE SCHEMA bugchase;
GO
DROP PROCEDURE IF EXISTS bugchase.worker_1;
DROP PROCEDURE IF EXISTS bugchase.worker_2;
DROP PROCEDURE IF EXISTS bugchase.runner_selectOnly;
DROP PROCEDURE IF EXISTS bugchase.runner_selectAndInsert;
GO
CREATE PROCEDURE bugchase.worker_1
AS
BEGIN
-- this worker just returns a value
SELECT
'Result Worker 1';
END;
GO
CREATE PROCEDURE bugchase.worker_2
AS
BEGIN
-- this worker encounters an error while processing.
-- the error will be catched and some data will be returned
DECLARE @result INT;
BEGIN TRY
-- this will force an error, because 'ABCD' could not be casted as float
SET @result = CAST('ABCD' AS FLOAT);
END TRY
BEGIN CATCH
-- just catch, don't do anything else
END CATCH;
SELECT
'Result Worker 2';
END;
GO
CREATE PROC bugchase.runner_selectAndInsert
AS
BEGIN
SET NOCOUNT ON;
DECLARE @result AS TABLE (value NVARCHAR(64));
-- exec worker_1 (no error will be thrown inside worker_1)
PRINT 'Select&Insert: Worker 1 start';
INSERT INTO @result (value)
EXEC bugchase.worker_1;
PRINT 'Select&Insert: Worker 1 end';
-- exec worker_2 (will throw and catch an error inside)
PRINT 'Select&Insert: Worker 2 start';
INSERT INTO @result (value)
EXEC bugchase.worker_2;
PRINT 'Select&Insert: Worker 2 end';
END;
GO
CREATE PROC bugchase.runner_selectOnly
AS
BEGIN
SET NOCOUNT ON;
-- exec worker_1 (no error will be thrown inside worker_1)
PRINT 'SelectOnly: Worker 1 start';
EXEC bugchase.worker_1;
PRINT 'SelectOnly: Worker 1 end';
-- exec worker_2 (will throw and catch an error inside)
PRINT 'SelectOnly: Worker 2 start';
EXEC bugchase.worker_2;
PRINT 'SelectOnly: Worker 2 end';
END;
GO
BEGIN TRY
-- because all errors are catched within the worker-procedures, there should no error occur by this call
EXEC bugchase.runner_selectAndInsert;
END TRY
BEGIN CATCH
-- indeed an error will occur while running worker 2
PRINT CONCAT('Select&Insert: ERROR:', ERROR_MESSAGE());
END CATCH;
GO
BEGIN TRY
-- for demonstration only, this will run as expected
EXEC bugchase.runner_selectOnly;
END TRY
BEGIN CATCH
-- No error will occur
PRINT CONCAT('SelectOnly: ERROR:', ERROR_MESSAGE());
END CATCH;
GO
DROP PROCEDURE bugchase.worker_2;
DROP PROCEDURE bugchase.worker_1;
DROP PROCEDURE bugchase.runner_selectOnly;
DROP PROCEDURE bugchase.runner_selectAndInsert;
DROP SCHEMA bugchase;
The script above will show the following result:
Select&Insert: Worker 1 start
Select&Insert: Worker 1 end
Select&Insert: Worker 2 start
Select&Insert: ERROR:
The current transaction cannot be committed and cannot support operations that write to the log file.
Roll back the transaction.
No error occurs, when the results are not stored into a table:
SelectOnly: Worker 1 start
SelectOnly: Worker 1 end
SelectOnly: Worker 2 start
SelectOnly: Worker 2 end