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
Related
I have SQL Server table with application and push the data to oracle table (3rd party) using link-server. I want to set an error handler if insertion is not successful the delete query will not be executed. Here's my query run in SQL Server Agent every 24 hours.
DELETE FROM oracle_tbl
--If insert into is not successful then rollback else commit--
INSERT INTO oracle_tbl
SELECT*
FROM
sqlserver_tbl
Here's the outline of a stored procedure that will demonstrate the behavior you're looking for. If the insert is not succcessful, i.e. 0 are rows are inserted, then an exception is thrown which will trigger the CATCH BLOCK which contains the rollback statement. If the INSERT is successful then the DELETE statement executes.
drop proc if exists dbo.stored_procedure_name;
go
create proc dbo.stored_procedure_name
#input nvarchar(max)=null,
#test_id bigint output,
#response nvarchar(max) output
as
set nocount on;
set xact_abort on;
begin transaction
begin try
declare
#o_id bigint,
#o_count bigint;
/* attempt to insert into table */
INSERT INTO oracle_tbl
SELECT *
FROM
sqlserver_tbl;
select #o_count=rowcount_big();
select #o_id=cast(scope_identity() as bigint);
/* if the insert failed, then throw an exception which rollback the transaction */
if #o_count<>1
throw 50000, 'No rows inserted', 1;
/* delete from table */
DELETE FROM oracle_tbl;
select
#test_id=#o_id,
#response=(select N'Ok' reply_message, #o_id o_id for json path, without_array_wrapper);
commit transaction;
end try
begin catch
select
#test_id=cast(0 as bigint),
#response=error_message() for json path, without_array_wrapper);
rollback transaction;
end catch
go
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;
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.
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)
CREATE PROCEDURE [dbo].[USP_TESTEXCEPTION]
#Param1 AS INT,
#Param2 AS INT
AS
BEGIN
SET NOCOUNT ON;
UPDATE TABLE1 SET COLUMN1 = #Param1
SELECT #Param1, #Param2; // This select is causing problem.
SELECT #Param/0;
UPDATE TABLE2 SET COLUMN2 = #Param2
END
I am working on a Spring MVC project with SQL server 2012 back end.
I am calling the following SP from my DAO layer and transaction is handled from application layer.
Problem is this procedure is not raising any exception in application layer and for that partial transaction occurred(TABLE1 is being updated while TABLE2 is not).
Why this SP is not raising exception in application layer?
you will need to use try catch and xact_abort on
alter proc usp_test
as
begin
set nocount on
set xact_abort on
begin try
select 1
select 1/0
select 2
end try
begin catch
declare #errormessg varchar(max)
raiserror(#errormessg,16,1);
end catch
end