Single script to insert multiple exceptions - sql-server

I use below script to insert orders transaction manually. This script processes one order at time (#orderId - using this variable here). I got a list of 200 orders, is there a way i can process all orders using single script?
DECLARE #return_value int, #exceptionId bigint, #createDate datetime
EXEC #return_value = [dbo].[uspInsertException]
#exceptionTypeCode = N'CreateCustomerAccount',
#exceptionSource = N'SOPS',
#exceptionCode = N'PUSH2EQ',
#exceptionDescription = N'CreateCustomerAccount exception MANUALLY pushed to EQ',
#request = N'',
#response = N'',
#orderId = 227614128,
#sourceSystem = N'OMS',
#exceptionStatusCode = N'Open',
#actorId = 1,
#exceptionSubTypeCode = NULL,
#exceptionId = #exceptionId OUTPUT,
#createDate = #createDate OUTPUT
SELECT #exceptionId as N'#exceptionId', #createDate as N'#createDate'
SELECT 'Return Value' = #return_value

Absolutely it can be done. The best way I have found is building nested classes in your application, and then pass it to sql where you can shred it with OPENXML or xPath depending on the size of the xml.
Depending on your needs you can also use a webservice, where you can place the classes and the code to connect to the database. The application then references the classes in the webservice, and passes the data in the class hierarchy format to the web service, which then parses the data and passes it as a full block of xml to the database, where in a stored procedure it is shredded and inserted. If you use this method, make sure you make your c# classes serializable.
You can easily retrieve data from the database as part of the stored proc by using for xml, and I would recommend wrapping it in a transaction so that you don't have half of a file inserted when an error occurs.
If you need some code samples, provide a better description of how you are passing your data to the database.
CREATE PROCEDURE [dbo].[sp_InsertExceptions]
-- Add the parameters for the stored procedure here
#pXML XML
AS
BEGIN
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #XML AS XML, #hDoc AS INT
print convert(varchar(max), #pRI)
EXEC sp_xml_preparedocument #hDoc OUTPUT, #pRI
{Put your shredding code here}
EXEC sp_xml_removedocument #hDoc
COMMIT TRANSACTION;
EXECUTE sp_GetErrors --This stored procedure is used to retrieve data
--previously inserted
END TRY
BEGIN CATCH
-- Execute error retrieval routine.
EXECUTE usp_GetErrorInfo; --This stored procedure gets your error
--information and can also store it in the
--database to track errors
-- Test XACT_STATE:
-- If 1, the transaction is committable.
-- If -1, the transaction is uncommittable and should
-- be rolled back.
-- XACT_STATE = 0 means that there is no transaction and
-- a commit or rollback operation would generate an error.
-- Test whether the transaction is uncommittable.
IF (XACT_STATE()) = -1
BEGIN
PRINT
N'The transaction is in an uncommittable state.' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
-- Test whether the transaction is committable.
IF (XACT_STATE()) = 1
BEGIN
PRINT
N'The transaction is committable.' +
'Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH;

Related

SQL Server / T-SQL : Raiserror cancels prior inserts

In a procedure, I want to make a test then Raiserror when it's actually the case. But before that, I want to log the error in a table. My code is like this
CREATE PROCEDURE proc
#val VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
DECLARE #test VARCHAR(50)
SELECT #test = test
FROM test_table
WHERE ...
IF #test IS NULL
BEGIN
INSERT INTO log_table VALUES (#val);
RAISERROR ('Invalid value : %i', 16, 1, #val);
END
END
The code compiles. When executed with a bad value, the error is raised, but the insert is cancelled.
I tried turning xact_abort and nocount on and off but had no luck.
I tried encapsulating the insert request in BEGIN TRANSACTION/COMMIT but still get the same result.
What I noticed, my log_table which has an auto-increment id, gets incremented even when those inserts are being cancelled.
How can I raise and error but still persist the insert request?
Thanks
Consider using THROW instead:
CREATE TABLE dbo.log_table (val varchar(50));
GO
CREATE PROCEDURE dbo.[proc] #val varchar(50)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
DECLARE #test varchar(50); --As i never set this, it'll go into the IF
IF (#test IS NULL)
BEGIN
INSERT INTO log_table
VALUES (#val);
THROW 51000, N'Invalid value.', 1;
END;
END;
GO
EXEC dbo.[proc] #val = 'Some Value';
GO
SELECT *
FROM dbo.log_table;
GO
DROP PROC dbo.[proc];
DROP TABLE dbo.log_table;
DB<>Fiddle
In order to write to a log table you have to rollback any pending transaction. Otherwise your log table INSERT may be rolled back by the calling code, or may fail because the transaction is doomed.
So something like:
CREATE Procedure myproc
#val varchar(50)
as
begin
set nocount on
set xact_abort on
begin transaction;
begin try
-- do stuff
commit transaction;
end try
begin catch
if ##trancount > 0 rollback;
declare #error_message varchar(max) = error_message()
INSERT INTO log_table values (#val);
throw;
end catch
end
So apparently, my procedure was working as expected in SQLServer side. The problem was that I was calling this procedure from Java/Spring native query method and had to be annotated with #Modifying and #Transactional since it's doing insertions. Thus when an exception is caught, it was automatically rolled back.
I didn't find a quick solution to bypass Spring's transaction. Now I think all I have to do is, catch the exception in App layer and log to the log_table in app layer too

MS-SQL Using TRANSACTION with an cursor and inside an stored procedure

Can anybody tell what is wrong with this stored procedure?
I want to use a transacation within an stored procedure but if I run this procedure I get the following error message: "The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION"
I don't get it... I tried so many options with the BEGIN and COMMIT commands...
P.S. It doesn't matter what's inside the while loop, the error still raises.
Any ideas?
The Error raises if I use the query window inside SSMS.
USE db
GO
DECLARE #return_value int,
#str_return nvarchar(100)
EXEC #return_value = [dbo].[proc_xxxx]
#guid = 'xxxxxxxxxxxxxxxxx',
#str_return = #str_return OUTPUT
SELECT #str_return as N'#str_return'
SELECT 'Return Value' = #return_value
GO
Maybe this is the reason?
-- Declare Cursor
DECLARE xx_cursor CURSOR FOR
SELECT GUID
FROM TABLE;
BEGIN TRY
BEGIN TRAN
OPEN xx_cursor;
-- Run through
FETCH NEXT FROM xx_cursor INTO #guid_xx;
WHILE ##FETCH_STATUS = 0
BEGIN
--DO SOMETHING
FETCH NEXT FROM xx_cursor INTO #guid_xx;
END;
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
SET #str_return = 'Error'
END CATCH
CLOSE xx_cursor;
DEALLOCATE xx_cursor;

How to prevent a specific INSERT statement from rolling back in a transaction

I have a very huge SP and I have a transaction in it. I am running an algorithm in the SP and if the algorithm doesn't succeed, the transaction gets rolled back.
I need to log some data even if the transaction gets rolled back, but when the transaction is rolled back, it also rolls back the logs as well. This is a normal behavior, but I need to exclude those log insert statements from the the rollback, so the transaction still gets logged.
I have a temp table called #MissingAllocationLines, and I insert my logs into that table. Then if it rollbacks, I need to insert all rows from #MissingAllocationLines into a real table called DLWMS_ALLOCATIONMISSINGLOG
Is that possible? My sample code is below
create table #MissingAllocationLines
(ALLOCATIONJOBID BIGINT,
ORDERID BIGINT,
ORDERDETAILID BIGINT,
ITEMID BIGINT,
STOCKQUANTITY BIGINT,
ORDERQUANTITY BIGINT)
BEGIN TRANSACTION
WHILE(.....)
BEGIN
INSERT INTO #MissingAllocationLines (ALLOCATIONJOBID,ORDERID,ORDERDETAILID,ITEMID,STOCKQUANTITY,ORDERQUANTITY)
VALUES (#ALLOCATIONJOBID,#OrderID,#OrderDetailID,#ItemID,ISNULL(#StockFreeQuantity, 0),ISNULL(#RemainingQuantity,0))
...
...
...
END
IF(#DONE=1)
BEGIN
COMMIT TRANSACTION
END
ELSE
BEGIN
ROLLBACK TRANSCATION
INSERT INTO DLWMS_ALLOCATIONMISSINGLOG (ALLOCATIONJOBID,ORDERID,ORDERDETAILID,ITEMID,STOCKQUANTITY,ORDERQUANTITY)
SELECT ALLOCATIONJOBID,ORDERID,ORDERDETAILID,ITEMID,STOCKQUANTITY,ORDERQUANTITY
FROM #MissingAllocationLines
END
Try using a table variable rather than a temp table. Table variables do not participate in a transaction.
http://zarez.net/?p=1977
http://www.sqlservercentral.com/blogs/steve_jones/2010/09/21/table-variables-and-transactions/
In catch block,before rolling back the transaction,do the following...
DECLARE #TABLE AS TABLE
(COL1 INT,
COL2 INT
...
)
INSERT INTO #TABLE
SELECT * FROM #TEMP TABLE
ROLLBACK TRANSCATION
INSERT INTO DLWMS_ALLOCATIONMISSINGLOG
select * from #table
References:
https://www.itprotoday.com/sql-server/table-variable-tip
You can use 'remote proc transaction promotion' as false and do logging using that linked server to local server: Code as below
begin tran outertran
insert into t values (1)
begin tran innertran
insert into localserver.tempdb.#log values (1)
commit tran innertran
IF (#Done)
begin
commit tran outertran
end
else begin
rollback tran outertran
end
select * from #log
EXEC sp_addlinkedserver #server = N'localserver',#srvproduct = N'',#provider = N'SQLNCLI', #datasrc = ##SERVERNAME
EXEC sp_serveroption localserver,N'remote proc transaction promotion','FALSE'
EXEC sp_serveroption localserver,N'RPC OUT','TRUE' -- Enable RPC to the given server
Check out the SAVE TRANSACTION command.

SQL Server XACT_ABORT with exclusion

I have a larger stored procedure which utilizes several TRY/CATCH blocks in order to catch and log individual errors. I have also wrapped a transaction around the entire contents of the procedure, so as to be able to roll back the entire thing in the event of an error raised somewhere along the way (in order to prevent a lot of messy cleanup); XACT_ABORT has been enabled since it would otherwise not roll back the entire transaction.
Key component:
There is a table in my database which gets a record inserted each time this procedure is run with the results of operations and details on what went wrong.
Funny thing is happening - actually, when I finally figured out what was wrong, it was pretty obvious... the the insert statement into my log table is getting rolled back as well, hence, if I am not running this out of SSMS, I will not be able to see that this was even run, as the rollback removes all trances of activity.
Question:
Would it be possible to have the entire transaction roll back with the exception of this single insert statement? I would still want to preserve the error message which I compile during the running of the stored procedure.
Thanks so much!
~Eli
Update 6/28
Here's a code sample of what I'm looking at. Key difference between this and the samples posed by #Alex and #gameiswar is that in my case, the try/catch blocks are all nested inside the single transaction. The purpose of this is to have multiple catches (for the multiple tables), though we would the entire mess to be rolled back even if the last update failed.
SET XACT_ABORT ON;
BEGIN TRANSACTION
DECLARE #message AS VARCHAR(MAX) = '';
-- TABLE 1
BEGIN TRY
UPDATE TABLE xx
SET yy = zz
END TRY
BEGIN CATCH
SET #message = 'TABLE 1 '+ ERROR_MESSAGE();
INSERT INTO LOGTABLE
SELECT
GETDATE(),
#message
RETURN;
END CATCH
-- TABLE 2
BEGIN TRY
UPDATE TABLE sss
SET tt = xyz
END TRY
BEGIN CATCH
SET #message = 'TABLE 2 '+ ERROR_MESSAGE();
INSERT INTO LOGTABLE
SELECT
GETDATE(),
#message
RETURN;
END CATCH
COMMIT TRANSACTION
You can try something like below ,which ensures you log the operation.This takes advantage of the fact that table variables dont get rollbacked..
Psuedo code only to give you idea:
create table test1
(
id int primary key
)
create table logg
(
errmsg varchar(max)
)
declare #errmsg varchar(max)
set xact_abort on
begin try
begin tran
insert into test1
select 1
insert into test1
select 1
commit
end try
begin catch
set #errmsg=ERROR_MESSAGE()
select #errmsg as "in block"
if ##trancount>0
rollback tran
end catch
set xact_abort off
select #errmsg as "after block";
insert into logg
select #errmsg
select * from logg
OK... I was able to solve this using a combination of the great suggestions put forth by Alex and GameisWar, with the addition of the T-SQL GOTO control flow statement.
The basic ideas was to store the error message in a variable, which survives a rollback, then have the Catch send you to a FAILURE label which will do the following:
Rollback the transaction
Insert a record into the log table, using the data from the aforementioned variable
Exit the stored procedure
I also use a second GOTO statement to make sure that a successful run will skip over the FAILURE section and commit the transaction.
Below is a code snippet of what the test SQL looked like. It worked like a charm, and I have already implemented this and tested it (successfully) in our production environment.
I really appreciate all the help and input!
SET XACT_ABORT ON
DECLARE #MESSAGE VARCHAR(MAX) = '';
BEGIN TRANSACTION
BEGIN TRY
INSERT INTO TEST_TABLE VALUES ('TEST'); -- WORKS FINE
END TRY
BEGIN CATCH
SET #MESSAGE = 'ERROR - SECTION 1: ' + ERROR_MESSAGE();
GOTO FAILURE;
END CATCH
BEGIN TRY
INSERT INTO TEST_TABLE VALUES ('TEST2'); --WORKS FINE
INSERT INTO TEST_TABLE VALUES ('ANOTHER TEST'); -- ERRORS OUT, DATA WOULD BE TRUNCATED
END TRY
BEGIN CATCH
SET #MESSAGE = 'ERROR - SECTION 2: ' + ERROR_MESSAGE();
GOTO FAILURE;
END CATCH
GOTO SUCCESS;
FAILURE:
ROLLBACK
INSERT INTO LOGG SELECT #MESSAGE
RETURN;
SUCCESS:
COMMIT TRANSACTION
I don't know details but IMHO general logic can be like this.
--set XACT_ABORT ON --not include it
declare #result varchar(max) --collect details in case you need it
begin transaction
begin try
--your logic here
--if something wrong RAISERROR(...#result)
--everything OK
commit
end try
begin catch
--collect error_message() and other into #result
rollback
end catch
insert log(result) values (#result)

save point within a transaction and store error message in sql server table

I have stored procedure which is for inserting data into a table. This procedure is called from asp.net application which handles the transaction start, commit and rollback functionality. Inside the stored procedure there is no transaction.
In this scenario my application is working fine and it is hosted in the live. Now, inside the store procedure, I have to add a new functionality to insert another table by linked server to another database and if error appears then I have to store it in the database.
We want to implement this insertion in such a way so that the previous sp will be working fine.
Noted that if error from the insertion of linked server comes then entire process is rolled back and also save point is not working. We can do this by dot net code but this is for more 40 modules, so I have to do this by sp . So, how can we implement this.
MSDN SAVE TRANSACTION
CREATE PROCEDURE [dbo].[SaveCustomer]
#Firstname nvarchar(50),
#Lastname nvarchar(50)
AS
DECLARE #ReturnCode int = 1 -- 1 - success
,#NewID int
---------------------------------------------- These variables are for TRY/CATCH RAISEERROR and BEGIN TRAN/SAVE POINT use
,#tranCounter int -- #TranCounter > 0 means an active transaction was started before the procedure was called.
,#errorMessage nvarchar(4000) -- echo error information to the caller. Message text.
,#errorSeverity int -- Severity.
,#errorState int; -- State
SET #tranCounter = ##TRANCOUNT;
IF #tranCounter > 0
SAVE TRANSACTION SaveCustomer_Tran;
ELSE
BEGIN TRANSACTION;
BEGIN TRY
INSERT dbo.Customer (
Firstname
,Lastname)
VALUES (
#Firstname
,#Lastname)
SET #NewID = scope_identity()
IF #tranCounter = 0
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF #tranCounter = 0
ROLLBACK TRANSACTION;
ELSE
IF XACT_STATE() <> -1
ROLLBACK TRANSACTION SaveCustomer_Tran;
SELECT #errorMessage = 'Error in [SaveCustomer]: ' + ERROR_MESSAGE(), #errorSeverity = ERROR_SEVERITY(), #errorState = ERROR_STATE();
RAISERROR (#errorMessage, #errorSeverity, #errorState);
SET #ReturnCode = -1
END CATCH
SELECT #ReturnCode as [ReturnCode], #NewID as [NewID]
GO
There are two accepted ways to escape the current transaction rollback tarpit.
One way is to use a looopback connection. Either via linked server, or from SQLCLR, connect back to the current server and write the INSERT, making sure DTC enrollment is not allowed. As the loopback connection is a different transaction, you can safely rollback in the original transaction and preserve the INSERT. This approach has the drawback that is very easy to deadlock yourself.
The other, less known, way is to use sp_trace_generateevent to fire off a user-configurable event. This may not seem much, but you can create event notifications for these events and then impelemnt handling that does the INSERT in processing the event. Eg. see Sql progress logging in transaction. This approach has the drawback of being difficult.

Resources