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
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
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
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 am use SQL Server 2008 R2 version.
I created procedure which includes all operation of table like select,insert, update,delete operation in one procedure.
Here is procedure i created.
create PROCEDURE [dbo].[My_proc]
#Operation nvarchar(50)
AS
BEGIN
SET NOCOUNT ON;
if(#Operation ='Insert')
Begin
--Insert query
End
if(#Operation ='Update')
Begin
--Update query
End
if(#Operation ='Delete')
Begin
--Delete query
End
if(#Operation ='Select')
Begin
--Select query
End
END
**which is better?
like above procedure example.
or
write separate query for each operation.**
I've got this simple SPROC:
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF EXISTS(SELECT UserName FROM Party WHERE UserName = #UserName)
BEGIN
--This means it exists, return it to ASP and tell us
SELECT 'This record already exists!'
END
ELSE
BEGIN
--This means the record isn't in there already, let's go ahead and add it
SELECT 'Record Added'
-- Insert statements for procedure here
INSERT INTO Party
(EmailAddress, UserName, LoginPin)
VALUES (#EmailAddress, #UserName, #LoginPin)
END
END
How do I throw an exception from the SPROC so that my .NET C# app can catch the error using a TRY CATCH block?
You would use RAISERROR in your stored procedure.
See this Microsoft article on the topic.
Also, see the MSDN reference on RAISERROR