SQL Server : Cannot roll back ...No transaction or savepoint of that name was found - sql-server

We have Perl script that bcp's data for various entities to stage tables , and then invokes a stored procedure via DBI(Autocommit=0) to move data from Stage-Core tables for these entities.
The stored procedure pseudo code is as below
-- GET DETAILS from stage to temproaray tables
SELECT * FROM <MAIN_STG_TBL> INTO #MAIN_TBL
SELECT * FROM <SUPP_STG_1> INTO #supp_1
SELECT * FROM <SUPP_STG_2> INTO #supp_2
.....
SELECT * FROM <SUPP_STG_10> INTO #supp_10
RAISERROR (<INFO_MSG>, 10, 1) WITH NOWAIT
-- for each entity in #MAIN_TBL
-- move datae from stage to core
WHILE_LOOP ( EACH <ENTRY> in #MAIN_TBL )
BEGIN TRY
BEGIN TRAN STG_CORE
INSERT INTO <MAIN_CORE> SELECT * FROM #MAIN_TBL WHERE ENTITY=<ENTRY>
INSERT INTO <SUPP_CORE_1> SELECT * FROM #supp_1 WHERE ENTITY=<ENTRY>
INSERT INTO <SUPP_CORE_1> SELECT * FROM #supp_2 WHERE ENTITY=<ENTRY>
.....
INSERT INTO <SUPP_CORE_10> SELECT * FROM #supp_10 WHERE ENTITY=<ENTRY>
COMMIT TRAN STG_CORE;
END TRY
BEGIN CATCH
ROLLBACK TRAN STG_CORE;
END CATCH
RAISERROR (<INFO_MSG>, 10, 1) WITH NOWAIT
END LOOP
RAISERROR (<INFO_MSG>, 10, 1) WITH NOWAIT
The stored procedure itself is invoked in a AutoCommit=0 connection , and is immediately followed with DBI->commit
ISSUE
For all failed entities 200/1000 entities mostly due to Primary Key Violation in some of the SUPP Tables seeing the message STAGE_TO_CORE_SP text=Cannot roll back STG_CORE. No transaction or savepoint of that name was found

Related

SQL Server - How do I Create/COMMIT Log Records during a Running Process that will potentially be ROLLEDBACK

I am having much difficulty attempting to replicate Logging as I had done in Oracle using PRAGRMA AUTOMOUS_TRANSACTION which allows you to COMMIT records to a LOG table while NOT COMMITing any other DML operations. I've been banging my head as to how more experienced SQL Server guys are LOGGING the Success or Errors of their Database Programs/Processes. Bascially how are experienced T-SQL guys logging in the middle of an active T-SQL program? Another way to put it... I have a HUGE process that is NOT to be COMMITed until the entire process executes without Critical Errors but I still need to log ALL Errors and if it's a Critical Error then ROLLBACK entire process but I still need the Logs.
Using MERGE as Example demonstrating my inability to COMMIT some records but ROLLING back others ...
I have TWO named Transactions in the below script (1. sourceData, 2. mainProcess)... This is my first attempt at trying to accomplish this goal. First the script INSERTs records using the 1st Transaction into a table without COMMIT. Then in Transaction 2 in the MERGE block I am INSERTing records into the Destination table and the Log table and I COMMIT transaction 2 (mainProcess).
I then AM ROLLING BACK the 1st named Transaction (sourceData)..
The issue is... EVERYTHING is getting ROLLED Back even though I explicitly COMMITed the mainProcess Transaction.
GO
/* temp table to simulate Log Table */
IF OBJECT_ID('tempdb..#LogTable') IS NULL
CREATE TABLE #LogTable
( Action VARCHAR(50),
primaryID INT,
secondaryID INT,
CustomID INT,
Note VARCHAR(200),
ConvDate DATE
) --DROP TABLE IF EXISTS #LogTable;
; /* SELECT * FROM #LogTable; TRUNCATE TABLE #LogTable; */
/* SELECT * FROM #ProductionSrcTable */
IF OBJECT_ID('tempdb..#ProductionSrcTable') IS NULL
CREATE TABLE #ProductionSrcTable( primaryKey INT, contactName VARCHAR(200), sourceKey INT )
; --DROP TABLE IF EXISTS #ProductionSrcTable; TRUNCATE TABLE #ProductionSrcTable;
/* SELECT * FROM #ProductionDestTable */
IF OBJECT_ID('tempdb..#ProductionDestTable') IS NULL
CREATE TABLE #ProductionDestTable( primaryKey INT, contactName VARCHAR(200), secondaryKey INT )
; --DROP TABLE IF EXISTS #ProductionDestTable; TRUNCATE TABLE #ProductionDestTable;
GO
/* Insert some fake data into Source Table */
BEGIN TRAN sourceData
BEGIN TRY
INSERT INTO #ProductionSrcTable
SELECT 1001 AS primaryKey, 'Jason' AS contactName, 789105 AS sourceKey UNION ALL
SELECT 1002 AS primaryKey, 'Jane' AS contactName, 789185 AS sourceKey UNION ALL
SELECT 1003 AS primaryKey, 'Sam' AS contactName, 788181 AS sourceKey UNION ALL
SELECT 1004 AS primaryKey, 'Susan' AS contactName, 681181 AS sourceKey
;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION sourceData
END CATCH
/* COMMIT below is purposely commented out in order to Test */
--COMMIT TRANSACTION sourceData
GO
BEGIN TRAN mainProcess
DECLARE #insertedRecords INT = 0,
#CustomID INT = 2,
#Note VARCHAR(200) = 'Test Temp DB Record Population via OUTPUT Clause',
#ConvDate DATE = getDate()
;
BEGIN TRY
MERGE INTO #ProductionDestTable AS dest
USING
(
SELECT src.primaryKey, src.contactName, src.sourceKey FROM #ProductionSrcTable src
) AS src ON src.primaryKey = dest.primaryKey AND src.sourceKey = dest.secondaryKey
WHEN NOT MATCHED BY TARGET
THEN
INSERT --INTO ProductionDestTable
( primaryKey, contactName, secondaryKey )
VALUES
( src.primaryKey, src.contactName, src.sourceKey )
/* Insert Output in Log Table */
OUTPUT $action, inserted.primaryKey, src.sourceKey, #CustomID, #Note, #ConvDate INTO #LogTable;
; /* END MERGE */
/* Store the number of inserted Records into the insertedRecords variable */
SET #insertedRecords = ##ROWCOUNT;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION mainProcess
END CATCH
;
--ROLLBACK TRANSACTION mainProcess
COMMIT TRANSACTION mainProcess
ROLLBACK TRANSACTION sourceData
PRINT 'Records Inserted:' + CAST(#insertedRecords AS VARCHAR);
/* END */
--SELECT ##TRANCOUNT

How to use BEGIN TRANSACTION with while loop in SQL Server?

How to use BEGIN TRANSACTION with while loop in SQL Server?
This query never finishes perhaps because it stops and look for COMMIT TRANSACTION after inserting one row (when #cnt = 1) but I don't want to COMMIT TRANSACTION because I want to see results before committing.
BEGIN TRANSACTION
DECLARE #cnt INT = 0;
WHILE #cnt <= 100
BEGIN
DECLARE #offset INT = 1
INSERT INTO totalSales (col1, col2)
SELECT
'Col1', ROW_NUMBER() OVER (ORDER BY col2) + #offset
FROM
sales
SET #cnt = #cnt + 1;
END;
So how I can check result before commit in while loop?
You should create a BEGIN TRAN outer (general), and inside loop while create a BEGIN TRAN inner (with a trans name).
Inside loop, if are conditions to rollbacks only for this iteration i use SAVE TRAN savepoint for not lose previous trans.
I 've created an example tests in loop while with conditional inserts and rollback savepoint:
declare #num int
set #num = 0
--drop table #test
create table #test (
valor Varchar(100)
)
begin tran
while #num <= 5
begin
begin transaction tran_inner
insert into #test (valor) values ('INSERT 1 INNER -> ' + convert(varchar(10),#num))
save transaction sv_inner
insert into #test (valor) values ('INSERT 2 EVEN - SAVEPOINT -> ' + convert(varchar(10),#num))
if #num % 2 = 0 begin
commit transaction sv_inner
end else begin
rollback transaction sv_inner
end
insert into #test (valor) values ('INSERT 3 INNER -> ' + convert(varchar(10),#num))
set #num = #num + 1
if ##trancount > 0 begin
commit transaction tran_inner
end
end
select valor from #test;
if ##trancount > 0 begin
commit tran
end
Return rows: 1, 2 if iteration even, and 3.
In the same batch (within the same transaction) you can simply issue a SELECT command to see the updated content of the table. Changes will be persisted when the COMMIT TRANSACTION statement is executed or reverted on ROLLBACK.
CREATE TABLE test (id INT IDENTITY(1,1), x VARCHAR(32));
GO
BEGIN TRANSACTION;
INSERT INTO test (x) VALUES ('a');
INSERT INTO test (x) VALUES ('b');
SELECT * FROM test;
ROLLBACK TRANSACTION;
Example: http://sqlfiddle.com/#!6/e4910/2
Alternatively you can use the INSERT INTO .. OUTPUT construct to output the result of the INSERT statement.
Docs: https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql
Outside the batch (using a second connection), you can use READ UNCOMMITTED isolation level to be able to read records not committed yet.
Docs: https://technet.microsoft.com/en-us/library/ms189122(v=sql.105).aspx
If you are saying it never finishes it sounds to me like you are getting some blocking going on because that loop runs just fine.
https://www.mssqltips.com/sqlservertip/2429/how-to-identify-blocking-in-sql-server/
I HIGHLY recommend using Adam Machanic's sp_WhoIsActive for this as well: http://whoisactive.com

Identity key counter increment by one although it is in TRY Catch and Transaction is roll-backed ? SSMS 2008

Identity counter increment by one although it is in TRY Catch and Transaction is roll-backed ? SSMS 2008 is there any way i can stop it +1 or rollback it too.
In order to understand why this happened, Let's execute below sample code first-
USE tempdb
CREATE TABLE dbo.Sales
(ID INT IDENTITY(1,1), Address VARCHAR(200))
GO
BEGIN TRANSACTION
INSERT DBO.Sales
( Address )
VALUES ( 'Dwarka, Delhi' );
ROLLBACK TRANSACTION
Now, Execution plan for above query is-
The second last operator from right Compute Scalar is computing value for [Expr1003]=getidentity((629577281),(2),NULL) which is IDENTITY value for ID column. So this clearly indicates that IDENTITY values are fetched & Incremented prior to Insertion (INSERT Operator). So its by nature that even transaction rollback at later stage once created IDENTITY value is there.
Now, in order to reseed the IDENTITY value to Maximum Identity Value present in table + 1, you need sysadmin permission to execute below DBCC command -
DBCC CHECKIDENT
(
table_name
[, { NORESEED | { RESEED [, new_reseed_value ] } } ]
)
[ WITH NO_INFOMSGS ]
So the final query should include below piece of code prior to rollback statement:-
-- Code to check max ID value, and verify it again IDENTITY SEED
DECLARE #MaxValue INT = (SELECT ISNULL(MAX(ID),1) FROM dbo.Sales)
IF #MaxValue IS NOT NULL AND #MaxValue <> IDENT_CURRENT('dbo.Sales')
DBCC CHECKIDENT ( 'dbo.Sales', RESEED, #MaxValue )
--ROLLBACK TRANSACTION
So it is recommended to leave it on SQL Server.
You are right and the following code inserts record with [Col01] equal to 2:
CREATE TABLE [dbo].[DataSource]
(
[Col01] SMALLINT IDENTITY(1,1)
,[Col02] TINYINT
);
GO
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO [dbo].[DataSource] ([Col02])
VALUES (1);
SELECT 1/0
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
END CATCH;
GO
INSERT INTO [dbo].[DataSource] ([Col02])
VALUES (1);
SELECT *
FROM [dbo].[DataSource]
This is by design (as you can see in the documentation:
Consecutive values after server restart or other failures –SQL Server
might cache identity values for performance reasons and some of the
assigned values can be lost during a database failure or server
restart. This can result in gaps in the identity value upon insert. If
gaps are not acceptable then the application should use its own
mechanism to generate key values. Using a sequence generator with the
NOCACHE option can limit the gaps to transactions that are never
committed.
I try using NOCACHE sequence but it does not work on SQL Server 2012:
CREATE TABLE [dbo].[DataSource]
(
[Col01] SMALLINT
,[Col02] TINYINT
);
CREATE SEQUENCE [dbo].[MyIndentyty]
START WITH 1
INCREMENT BY 1
NO CACHE;
GO
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO [dbo].[DataSource] ([Col01], [Col02])
SELECT NEXT VALUE FOR [dbo].[MyIndentyty], 1
SELECT 1/0
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
END CATCH;
GO
INSERT INTO [dbo].[DataSource] ([Col01], [Col02])
SELECT NEXT VALUE FOR [dbo].[MyIndentyty], 1
SELECT *
FROM [dbo].[DataSource]
DROP TABLE [dbo].[DataSource];
DROP SEQUENCE [dbo].[MyIndentyty];
You can use MAX to solve this:
CREATE TABLE [dbo].[DataSource]
(
[Col01] SMALLINT
,[Col02] TINYINT
);
BEGIN TRY
BEGIN TRANSACTION;
DECLARE #Value SMALLINT = (SELECT MAX([Col01]) FROM [dbo].[DataSource]);
INSERT INTO [dbo].[DataSource] ([Col01], [Col02])
SELECT #Value, 1
SELECT 1/0
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
END CATCH;
GO
DECLARE #Value SMALLINT = ISNULL((SELECT MAX([Col01]) FROM [dbo].[DataSource]), 1);
INSERT INTO [dbo].[DataSource] ([Col01], [Col02])
SELECT #Value, 1
SELECT *
FROM [dbo].[DataSource]
DROP TABLE [dbo].[DataSource];
But you must pay attentions to your isolation level for potential issues:
If you want to insert many rows at the same time, do the following:
get the current max value
create table where to store the rows (that are going to be inserted) generating ranking (you can use identity column, you can use ranking function) and adding the max value to it
insert the rows

Exit Gracefully from Stored Procedure and avoid BEGIN/COMMIT mismatch

After having some trouble trapping SQL errors in my VBA application, I redesigned my stored procedures so that if an error occurs, the return value is the error code and an output variable contains the error message. I do not re-throw the error in my catch blocks. I'll call this a "graceful exit" for lack of a better term. It has made things easier on the client-side, but now I have an issue when a trigger fired by a nested stored procedure rolls back a transaction.
Take the below example. TEST_INNER_PROC begins with a ##TRANCOUNT of 1, performs an insert which fires the trigger, which rolls back the transaction, and when TEST_INNER_PROC exits it throws error
266: Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements
Normally, I would pattern both of these procedures the same; I've simplified them here. The inner procedure does not attempt to start a transaction (it wouldn't make a difference), and the outer procedure does re-throw the error, just so that I can see the error information printed. Normally, I would return the error code to the client via the return code and #ERR_MSG output variable.
I like #gbn's pattern here: Nested stored procedures containing TRY CATCH ROLLBACK pattern? However, It does not appear to accommodate my "graceful exit" if the rollback happens in a trigger. I'm also not sure if Rusanu's pattern
would accommodate it either.
CREATE TABLE TEST (
COL1 INT
)
GO
CREATE TRIGGER TEST_TRIGGER
ON TEST FOR INSERT
AS
BEGIN TRY
THROW 50001, 'TEST Trigger produced an error.', 1
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 AND XACT_STATE()<>0
ROLLBACK TRAN;
THROW
END CATCH
GO
CREATE PROC TEST_INNER_PROC
AS
SET NOCOUNT, XACT_ABORT ON
DECLARE #RTN INT = 0
BEGIN TRY
INSERT TEST (COL1) VALUES (1)
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 AND XACT_STATE()<>0
ROLLBACK TRAN
SET #RTN = ERROR_NUMBER();
--THROW
END CATCH
RETURN #RTN
GO
CREATE PROC TEST_OUTER_PROC
AS
SET NOCOUNT, XACT_ABORT ON
DECLARE #RTN INT = 0
BEGIN TRY
BEGIN TRAN
EXEC #RTN = TEST_INNER_PROC
IF #RTN <> 0 THROW 50000, 'Execution of TEST_INNER_PROC produced an error.', 1
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 AND XACT_STATE()<>0
ROLLBACK TRAN;
THROW
END CATCH
GO
EXEC TEST_OUTER_PROC
GO
DROP TABLE TEST
DROP PROC TEST_OUTER_PROC
DROP PROC TEST_INNER_PROC
GO
The above code results in:
Msg 266, Level 16, State 2, Procedure TEST_INNER_PROC, Line 63
Transaction count after EXECUTE indicates a mismatching number of BEGIN
and COMMIT statements. Previous count = 1, current count = 0.
But if you uncomment the "THROW" statement in TEST_INNER_PROC, it throws:
Msg 50001, Level 16, State 1, Procedure TEST_TRIGGER, Line 69
TEST Trigger produced an error.
which is the error I want to handle in TEST_OUTER_PROC.
Is it possible to use stored procedures that "exit gracefully", returning the error code and error message as variables, and avoid the mismatching number of BEGIN and COMMIT statement?
You can store all the errors in a table and select them from it
CREATE TABLE LOG_ERROR (
SPID INT DEFAULT ##SPID
,DATE DATETIME DEFAULT GETDATE()
,ERROR_NUMBER INT DEFAULT ERROR_NUMBER())
In your procedures:
begin try
--code...
end try
begin catch
INSERT LOG_ERROR DEFAULT VALUES;
throw
end catch
After the procedure execution:
SELECT * FROM LOG_ERROR
WHERE SPID = ##SPID
AND DATE > CONVERT(DATE,GETDATE()) --Errors from today
Update: Create a view so you get the text too:
CREATE VIEW VW_LOG_ERROR AS
SELECT
E.*
,M.TEXT
FROM LOG_ERROR E
JOIN SYS.MESSAGES M WITH(NOLOCK) ON E.ERROR_NUMBER = M.MESSAGE_ID
JOIN SYS.SYSLANGUAGES L
ON M.LANGUAGE_ID = L.MSGLANGID
AND L.LANGID = ##LANGID
WHERE
SPID = ##SPID
AND DATE > CONVERT(DATE,GETDATE()) --Errors from today

Insertion transaction- rollback SQL

I am new to databases and i try to create a stored procedure that inserts data in tables that are in a many to many relation.If any part of the operation fails then it must try to recover as much as possible from the entire operation. For example, if one wants to create a record regarding publishers and books and succeeds creating the publisher but fails with the book, then it should roll back
the creation of the book, but not of the publisher.
My code looks like this:
BEGIN TRY
BEGIN TRANSACTION
DECLARE #serviciuKey int
DECLARE #specializareKey int
IF NOT EXISTS (SELECT denumire, moneda, pret FROM Serviciu where denumire=#denumire and moneda=#moneda and pret=#pret)
BEGIN
INSERT INTO Serviciu ( denumire, moneda, pret)
VALUES (#denumire, #moneda, #pret)
END
SET #serviciuKey=##IDENTITY
SAVE TRANSACTION savepoint
IF NOT EXISTS (SELECT denumire, descriere FROM Specializare where denumire=#denumire_spec AND descriere=#descriere)
BEGIN
INSERT INTO Specializare( denumire, descriere)
VALUES (#denumire_spec, #descriere)
END
SET #specializareKey=##IDENTITY
SAVE TRANSACTION savepoint
IF NOT EXISTS (SELECT * FROM Specializare_Serviciu where cod_specializare=#specializareKey and cod_serviciu=#serviciuKey)
BEGIN
INSERT INTO Specializare_Serviciu( cod_specializare, cod_serviciu)
VALUES (#specializareKey, #serviciuKey)
END
SAVE TRANSACTION savepoint
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##trancount > 0 ROLLBACK TRANSACTION savepoint
DECLARE #msg nvarchar(2048) = error_message()
RAISERROR (#msg, 16, 1)
RETURN 55555
END CATCH
When i execute the procedure, i have this error:
Msg 3931, Level 16, State 1, Procedure AddData0, Line 76
The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
Also when i try to insert some data that already exists, it is inserted with another ID, which means that IF NOT EXIST statement is not working.
Any help, please?
Try this version:
DECLARE #serviciuKey int
DECLARE #specializareKey int
BEGIN TRY
BEGIN TRAN
IF NOT EXISTS (SELECT denumire, moneda, pret FROM Serviciu where denumire=#denumire and moneda=#moneda and pret=#pret)
BEGIN
INSERT INTO Serviciu ( denumire, moneda, pret)
VALUES (#denumire, #moneda, #pret)
END
SET #serviciuKey=scope_identity()
IF NOT EXISTS (SELECT denumire, descriere FROM Specializare where denumire=#denumire_spec AND descriere=#descriere)
BEGIN
INSERT INTO Specializare( denumire, descriere)
VALUES (#denumire_spec, #descriere)
END
SET #specializareKey=scope_identity()
IF NOT EXISTS (SELECT * FROM Specializare_Serviciu where cod_specializare=#specializareKey and cod_serviciu=#serviciuKey)
BEGIN
INSERT INTO Specializare_Serviciu( cod_specializare, cod_serviciu)
VALUES (#specializareKey, #serviciuKey)
END
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##trancount > 0
ROLLBACK TRAN
DECLARE #msg nvarchar(2048) = error_message()
RAISERROR (#msg, 16, 1)
RETURN 55555
END CATCH

Resources