I wrote a procedure that calls another procedure through link server, it has try catch block and catch block works very well in case of most of the errors, for example when I want to "insert a NULL value to a not null able column" catch block is executed successfully and log error result to my table,but when it comes to link server timeout, catch block is not working, I don't know where I missed things up that caused this issue.
You can take a look at my procedure
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
DECLARE #BeforeCall DATETIME,#AfterCall DATETIME,#DiffSec BIGINT,#CycleId BIGINT
DECLARE #TBL TABLE(Date INT,Time INT,Branch INT,Amount BIGINT)
----------------Get Last CycleId------------------------------------------
SELECT TOP 1 #CycleId=CycleId FROM dbo.BranchesResetDate_History
ORDER BY CycleId DESC
SET #CycleId=ISNULL(#CycleId,0)+1;
------------------------------------------------------------------
-----------------Get Reset Date--------------------------------------------
SET #BeforeCall=GETDATE()
INSERT INTO #TBL
(
Date,
Time,
Amount,
Branch
)
EXEC [AB_TO_FK].[xxxx].dbo.GetResetDate
SET #AfterCall=GETDATE()
SET #DiffSec=DATEDIFF(SECOND,#BeforeCall,#AfterCall)
-------------------------------------------------------------------
--------------------Log Execute Result To History------------------
INSERT INTO dbo.BranchesResetDate_History (Date,Time,Branch,Amount,InsertionDateTime,SecondsElapsed,CycleId)
SELECT Date,Time,Branch,Amount,dbo.DateTimeMDToSHD(GETDATE()),#DiffSec,#CycleId
FROM #TBL
--------------------------------------------------------------------
---------------------Insert Result To Main Table--------------------
INSERT INTO dbo.BranchesResetDate
(
ResetDate,
ResetTime,
BranchCode,
Amount,
InsertionDateTime
)
SELECT Date,
Time,
Branch,
Amount,
dbo.DateTimeMDToSHD(GETDATE())
FROM #TBL
-------------------------------------------------------------------
-------------Delete Duplicate Records------------------------------
DELETE res FROM (
SELECT id,ROW_NUMBER() OVER(PARTITION BY BranchCode,ResetDate,ResetTime,Amount ORDER BY ResetTime)rn FROM dbo.BranchesResetDate
) res WHERE res.rn>1
-------------------------------------------------------------------
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT>0
ROLLBACK TRANSACTION;
------------------------Log Exception------------------------------
INSERT INTO dbo.Proc_Exception (ErrorNumber,ErrorSeverity,ErrorState,ErrorProcedure,ErrorLine,ErrorMessage,DateTime)
SELECT ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
ERROR_PROCEDURE(),
ERROR_LINE(),
ERROR_MESSAGE(),
dbo.DateTimeMDToSHD(GETDATE())
-------------------------------------------------------------------
END CATCH
END
GO
When it reaches to line below
EXEC [AB_TO_FK].[xxxx].dbo.GetResetDate
In case of timeout error, catch block does not executed and I just get this error
OLE DB provider "SQLNCLI11" for linked server "AB_TO_FK" returned message "Query timeout expired
Timeout is always caused by the client (here the server with the linked server definition), which cancels the running query by sending a TDS Attention event. The error message you see is generated locally, and (for reasons lost to history) does not have a high-enough severity to trigger a CATCH block or flip ##error.
The recommended solution is to prevent the linked server timeout.
Either by extending it, eg setting the remote query timeout to 0 (infinite wait), by using this code:
sp_configure 'remote query timeout', 0
go
reconfigure with override
go
as per this support article, or by causing the remote call to fail with a trappable error instead of timeout. EG you can SET LOCK TIMEOUT in the procedure or in the batch sent to the remote server before running the procedure.
If the remote procedure succeeds it should return 0. If it fails it should return some non-zero value, and if it's canceled due to a timeout it will return null. So you can check like this:
declare #r int
execute #r = [AB_TO_FK].[xxxx].dbo.GetResetDate
if (#r is null or #r <> 0)
begin
throw 51000, 'Linked Server Procedure failed. Possible timeout.', 1;
end
And you can capture both the stored procedure return code and a resultset. And if it's supposed to output a resultset, you can also determine if it failed by checking the table after the execute. EG
declare #t table (rc int)
declare #r int = -1
insert into #t(id)
execute #r = Loopback.tempdb.dbo.bar
if (#r is null or #r <> 0 or 0 = (select count(*) from #t) )
begin
throw 51000, 'Linked Server Procedure failed. Possible timeout.', 1;
end
Related
Hoping someone can help. I'm trying to stop a stored procedure from returning a recordset that has been selected, if an error is encountered later in the stored procedure. I've included some pseudo code below to show what I'm doing. Basically, the SELECT [Foo] is returning a recordset if the or COMMIT actions fail and [Tran1] is rolled back. The client does not support multiple recordsets and the has to come after the SELECT so I'm looking for a command to place in the CATCH block that effectively cancels the SELECT [Foo] and instead enables me to return the recordset created by SELECT -1 AS [Error_Code]
BEGIN TRANSACTION [Tran1]
BEGIN TRY
SET NOCOUNT ON;
<Do some Update>
SELECT [Foo]
FROM [Bar]
<Do some Insert>
COMMIT TRANSACTION [Tran1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [Tran1]
SELECT -1 AS Error_Code
END CATCH
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 have to develop a WinCC Visual Basic Script management application. In this application I read an XML archive, and after that I put the information in a SQL database through a SQL INSERT query.
My problem is that I don't know how to do the error handling to view the SQL errors in VBScript MsgBox for example.
Activating the error handling with On Error Resume Next and after the evaluation of these errors with If Err.Number <> 0 Then ... the errors produced in SQL Server don't appear in VBScript.
If you want to get SQL Server error, You can use stored procedure with transaction to insert data into table:
create procedure dbo.InsertTable (
#param1 nvarchar(80)
,#param2 nvarchar(80)
,#error_text nvarchar(400) output)
as
begin
begin tran
begin try
insert into YourTable (column1, column2)
values (#param1, #param2)
end try
begin catch
set #error_text = error_message()
rollback
return
end catch
commit
end
Now You will get eventually error from the output parameter #error_text
declare #error_text nvarchar(400)
exec dbo.InsertTable 'Value1','Value2', #error_text output
select #error_text
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)
First sorry for my English.
I have one server when receives an update in specific table, I want write to a remote server too, but if the remote server is unavailable, I want the trigger to write to the local server in another temp table.
Example code to write to remote server:
-- REMOTO is remote server
CREATE TRIGGER insertin
ON mangas
AFTER INSERT
AS
BEGIN
DECLARE #serie varchar(max), #capitulo int
SELECT #serie = serie ,#capitulo = capitulo
FROM inserted
INSERT INTO [REMOTO].[Gest].[dbo].[MARCA] (Codigo, Descripcion)
VALUES (#capitulo, #serie)
END
I need, for example, something like TRY...CATCH or similar. I don't know how can I do it.
Thanks for answers and sorry for my English again.
If using SQL Server 2005 or later, you can put a TRY...CATCH block round your INSERT statement. See MSDN: http://msdn.microsoft.com/en-US/library/ms175976(v=sql.90).aspx
BEGIN TRY
INSERT INTO [REMOTO].[Gest].[dbo].[MARCA]
(Codigo, Descripcion)
VALUES
( #capitulo, #serie )
END TRY
BEGIN CATCH
INSERT INTO [dbo].[MyTemporaryTable]
(Codigo, Descripcion)
VALUES
( #capitulo, #serie )
END CATCH
i do with this code and works well.
if anyone has better solution , please post, I'm learning t-sql now.
any advice is well come
begin
declare
#serie varchar(max),
#capitulo int,
#maxMarca int
select #serie = serie
from inserted
select #maxMarca =max(Codigo) from [REMOTO].[Gest].[dbo].[MARCA]
set #maxMarca = #maxMarca+1
commit -- save transaction insert which generates this trigger work.
begin TRANSACTION
BEGIN TRY
INSERT INTO [REMOTO].[Gest].[dbo].[MARCA] (Codigo, Descripcion) VALUES ( #maxMarca, #serie)
commit transaction --save transaction and finish, if remote server work
END TRY
BEGIN CATCH
IF ##trancount > 0
begin
rollback transaction --remote transaction is go back
INSERT INTO [mangas].[dbo].[mangasTemp] VALUES (#maxMarca, #serie)
commit transaction -- save transaction in local temporal table.
end
END CATCH
end
go