I have a SQL script that does quite a few updates. When I retrieve the script (in a Classic ASP page) from a text file and attempt to execute it, it does not appear to apply all the updates I expect yet if I copy and paste the content into Management Studio, the query works fine. I cannot work out why this might be the case. Is there a limit as to how many statements/operations you can include in such a script?
Also the execute command oConn.Execute strSql does not fail, it just moves to the next line of classic ASP code. Is there any way I can test for errors?
Unfortunately I don't have enough reputation to comment on your question, but have you tried wrapping your code in TRANSACTION blocks? Very useful if things go wrong.
As for error handling, you can refer to the ##ERROR...
DECLARE #ErrorVar INT
RAISERROR(N'Message', 16, 1);
IF ##ERROR <> 0
-- This PRINT statement prints 'Error = 0' because
-- ##ERROR is reset in the IF statement above.
PRINT N'Error = ' + CAST(##ERROR AS NVARCHAR(8));
GO
Paul set me on the right track here, thank you. I've been coding SQL for years but confess I have dabbled only a little in the world of transactions. After the code below I put other stats and the failure message into a table that I interrogate in the ASP code after calling the script.
BEGIN TRY
BEGIN TRANSACTION
--My long update script placed in here
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
SET #FailMsg = ERROR_MESSAGE() + ' Failed at line ' + CAST(ERROR_LINE() as varchar) + '.'
END
END CATCH
Related
I have a stored procedure which is runs automatically every morning in SQL Server 2008 R2, part of this stored procedure involves executing other stored procedures. The format can be summarised thus:
BEGIN TRY
-- Various SQL Commands
EXECUTE storedprocedure1
EXECUTE storedprocedure2
-- etc
END TRY
BEGIN CATCH
--This logs the error to a table
EXECUTE errortrappingprocedure
END CATCH
storedprocedure1 and storedprocedure2 basically truncate a table and select into it from another table. Something along the lines of:
BEGIN TRY
TRUNCATE Table1
INSERT INTO Table1 (A, B, C)
SELECT A, B, C FROM MainTable
END TRY
BEGIN CATCH
EXECUTE errortrappingprocedure
END CATCH
The error trapping procedure contains this:
INSERT INTO
[Internal].dbo.Error_Trapping
(
[Error_Number],
[Error_Severity],
[Error_State],
[Error_Procedure],
[Error_Line],
[Error_Message],
[Error_DateTime]
)
(
SELECT
ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
ERROR_PROCEDURE(),
ERROR_LINE(),
ERROR_MESSAGE(),
GETDATE()
)
99% of the time this works, however occasionally we will find that storedprocedure1 hasn't completed, with Table1 only being part populated. However no errors are logged in our error table. I've tested the error trapping procedure and it does work.
When I later run storedprocedure1 manually it completes fine. No data in the source table will have changed by this point so it's obviously not a problem with the data, something else has happened in that instant which has caused the procedure to fail. Is there a better way for me to log errors here, or somewhere else within the database I can look to try and find out why it is failing?
Try to use SET ARITHABORT (see link). It must ROLLBACK in your case. Also the answer of #Kartic seem reasonable.
I recommned also to read about implicit and explicit transactions - I think that this is your problem. You have several implicit transactions and when error happeneds you are in the middle of the job - so only part is rollbackеd and you have some data in that tables.
There are some type of Errors that TRY..CATCH block will not handle them, look here for more information https://technet.microsoft.com/en-us/library/ms179296(v=sql.105).aspx . for such Errors you should handle them in your application.
also I think you might have transaction management problem in your application too.
I am not sure if I understood you completely. Below code is too big for comment. So posting as an answer for your reference. If this is not what you want, I'll delete it.
Can we add transaction handling part as well.
DECLARE #err_msg NVARCHAR(MAX)
BEGIN TRY
BEGIN TRAN
-- Your code goes here
COMMIT TRAN
END TRY
BEGIN CATCH
SET #err_msg = ERROR_MESSAGE()
SET #err_msg = REPLACE(#err_msg, '''', '''''')
ROLLBACK TRAN
-- Do something with #err_msg
END CATCH
In one StoredProcedure, I am using a linked server to do some inserts/updates in another DB. Have wrapped the code in a transaction.
CREATE PROCEDURE [mySchema].[mySP] (paramList)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE #TranStarted AS BIT = 0;
BEGIN TRY
IF ##TRANCOUNT = 0
BEGIN
BEGIN TRANSACTION;
SET #TranStarted = 1;
END
-- Do some stuff with linked server (inserts and updates on another server)
IF #TranStarted = 1
BEGIN
COMMIT TRANSACTION;
END
END TRY
BEGIN CATCH
IF #TranStarted = 1
BEGIN
ROLLBACK TRANSACTION;
END
PRINT 'ERROR_NUMBER: ' + CAST(ERROR_NUMBER() AS NVARCHAR(10));
PRINT 'ERROR_MESSAGE: ' + ERROR_MESSAGE();
PRINT 'ERROR_SEVERITY: ' + CAST(ERROR_SEVERITY() AS NVARCHAR(2));
PRINT 'ERROR_STATE: ' + CAST(ERROR_STATE() AS NVARCHAR(3));
PRINT 'ERROR_PROCEDURE: ' + ERROR_PROCEDURE();
PRINT 'ERROR_LINE: ' + CAST(ERROR_LINE() AS NVARCHAR(5));
END CATCH
END
And here is the result:
ERROR_NUMBER: 3910
ERROR_MESSAGE: Transaction context in use by another session.
ERROR_SEVERITY: 16
ERROR_STATE: 2
ERROR_LINE: 1
I've seen this:
Related track but I think I don't have any loopback linked servers
The StoredProcedure works fine without transaction block.
Any help would be greatly appreciated.
This might not fit your scenario, but just in case it does fit your's or someone else's...
I encountered the "Transaction context is in use..." error when updating a table with a trigger referencing a linked server that is on the same SQL server and the connection string included ";MultipleActiveResultSets=true". The application I was adding script to needed that functionality.
If, however, I use a connection string without MultipleActiveResultSets, the application was able to successfully update the table. In our PROD environment, the linked server is connecting to a different server, but in our DEV environment, it links back to the same server. With MultipleActiveResultSets set to true, SQL server would attempt to create a distributed transaction and fail because it is a loopback (which many posts have indicated is not supported by DTC).
With MultipleActiveResultSets set to false (or left out of the connection string), SQL server does not have a problem with the loopback.
I'm having a similar issue to The current transaction cannot be committed and cannot support operations that write to the log file, but I have a follow-up question.
The answer there references Using TRY...CATCH in Transact-SQL, which I'll come back to in a second...
My code (inherited, of course) has the simplified form:
SET NOCOUNT ON
SET XACT_ABORT ON
CREATE TABLE #tmp
SET #transaction = 'insert_backtest_results'
BEGIN TRANSACTION #transaction
BEGIN TRY
--do some bulk insert stuff into #tmp
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION #transaction
SET #errorMessage = 'bulk insert error importing results for backtest '
+ CAST(#backtest_id as VARCHAR) +
'; check backtestfiles$ directory for error files ' +
' error_number: ' + CAST(ERROR_NUMBER() AS VARCHAR) +
' error_message: ' + CAST(ERROR_MESSAGE() AS VARCHAR(200)) +
' error_severity: ' + CAST(ERROR_SEVERITY() AS VARCHAR) +
' error_state ' + CAST(ERROR_STATE() AS VARCHAR) +
' error_line: ' + CAST(ERROR_LINE() AS VARCHAR)
RAISERROR(#errorMessage, 16, 1)
RETURN -666
END CATCH
BEGIN TRY
EXEC usp_other_stuff_1 #whatever
EXEC usp_other_stuff_2 #whatever
-- a LOT of "normal" logic here... inserts, updates, etc...
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION #transaction
SET #errorMessage = 'error importing results for backtest '
+ CAST(#backtest_id as VARCHAR) +
' error_number: ' + CAST(ERROR_NUMBER() AS VARCHAR) +
' error_message: ' + CAST(ERROR_MESSAGE() AS VARCHAR(200)) +
' error_severity: ' + CAST(ERROR_SEVERITY() AS VARCHAR) +
' error_state ' + CAST(ERROR_STATE() AS VARCHAR) +
' error_line: ' + CAST(ERROR_LINE() AS VARCHAR)
RAISERROR(#errorMessage, 16, 1)
RETURN -777
END CATCH
RETURN 0
I think I have enough information to just play with it and figure it out myself... unfortunately reproducing the error is proving damn near impossible. So I'm hoping that asking here will help clarify my understanding of the problem and solution.
This stored procedure is, intermittently, throwing errors like this one:
error importing results for backtest 9649 error_number: 3930 error_message: The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction. error_severity: 16 error_state 1 error_line: 217
So obviously the error is coming from the 2nd catch block
Based on what I've read in Using TRY...CATCH in Transact-SQL, I think what's happening is that when the exception is thrown, the use of XACT_ABORT is causing the transaction to be "terminated and rolled back"... and then the first line of the BEGIN CATCH is blindly attempting to roll back again.
I don't know why the original developer enabled XACT_ABORT, so I'm thinking the better solution (than removing it) would be to use XACT_STATE() to only roll back if there is a transaction (<>0). Does that sound reasonable? Am I missing something?
Also, the mention of logging in the error message makes me wonder: Is there another problem, potentially with configuration? Is our use of RAISEERROR() in this scenario contributing to the problem? Does that get logged, in some sort of case where logging isn't possible, as the error message alludes to?
You always need to check for XACT_STATE(), irrelevant of the XACT_ABORT setting. I have an example of a template for stored procedures that need to handle transactions in the TRY/CATCH context at Exception handling and nested transactions:
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(),
#message = ERROR_MESSAGE(),
#xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end
There are a few misunderstandings in the discussion above.
First, you can always ROLLBACK a transaction... no matter what the state of the transaction. So you only have to check the XACT_STATE before a COMMIT, not before a rollback.
As far as the error in the code, you will want to put the transaction inside the TRY. Then in your CATCH, the first thing you should do is the following:
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION #transaction
Then, after the statement above, then you can send an email or whatever is needed. (FYI: If you send the email BEFORE the rollback, then you will definitely get the "cannot... write to log file" error.)
This issue was from last year, so I hope you have resolved this by now :-)
Remus pointed you in the right direction.
As a rule of thumb... the TRY will immediately jump to the CATCH when there is an error. Then, when you're in the CATCH, you can use the XACT_STATE to decide whether you can commit. But if you always want to ROLLBACK in the catch, then you don't need to check the state at all.
I have encountered this error while updating records from table which has trigger enabled.
For example - I have trigger 'Trigger1' on table 'Table1'.
When I tried to update the 'Table1' using the update query - it throws the same error. THis is because if you are updating more than 1 record in your query, then 'Trigger1' will throw this error as it doesn't support updating multiple entries if it is enabled on same table.
I tried disabling trigger before update and then performed update operation and it was completed without any error.
DISABLE TRIGGER Trigger1 ON Table1;
Update query --------
Enable TRIGGER Trigger1 ON Table1;
I encountered a similar issue to the above and was receiving the same error message. The above answers were helpful but not quite what I needed, which was actually a bit simpler.
I had a stored procedure that was structured as below:
SET XACT_ABORT ON
BEGIN TRY
--Stored procedure logic
BEGIN TRANSACTION
--Transaction logic
COMMIT TRANSACTION
--More stored procedure logic
END TRY
BEGIN CATCH
--Handle errors gracefully
END CATCH
TRY...CATCH was used to handle errors in the stored procedure logic. Just one part of the procedure contained a transaction, and if an error occurred during this it would not get picked up by the CATCH block, but would error out with the SQL Transaction Error message.
This was resolved by adding another TRY...CATCH wrapper that would ROLLBACK the transaction and THROW the error. This meant any errors in this step could be handled gracefully in the main CATCH block, as per the rest of the stored procedure.
SET XACT_ABORT ON
BEGIN TRY
--Stored procedure logic
BEGIN TRY
BEGIN TRANSACTION;
--Transaction logic
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK;
THROW;
END CATCH
--More stored procedure logic
END TRY
BEGIN CATCH
--Handle errors gracefully
END CATCH
None of this helped me so here is what fixed my issue.
A teammate configured a server trigger to monitor DDL changes.
Once I disabled it, I could install the package then I enabled it again and package is still working.
Had the exact same error in a procedure.
It turns out the user running it (a technical user in our case) did not have sufficient rigths to create a temporary table.
EXEC sp_addrolemember 'db_ddladmin', 'username_here';
did the trick
I am using SQL Server 2008 and when I run this Statement in Management studio the Select statement in the Catch Block is executed as expected
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
However when I run this statement the statement in the Catch Block is never executed and instead the error is just displayed in the results tab
BEGIN TRY
Select * from IDontExist
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
They both return the same error number '208' 'Invalid Object Name: IDontExist' so why would one get handled and the other not?
I don't get the CATCH block hit at all.
That's because the code won't compile, because the object doesn't exist, no plan is generated, so nothing runs to hit the CATCH block.
You can never hit this catch block so somethign is wrong with your testing/example. You can hit an outer catch block in a different scope (eg nested stored procs)
Edit: I'm using SQL Server 2005 SP3
It depends when deferred name resolution applies, related to statement level recompilation.
In my case, the whole batch fails both times and no statement level recompilation happens so no deferred name resolution
In OP's case, the batch compiles and runs but then has a statement level recompilation/deferred name resolution error in running code
I'm off to find some references about why it's different, given BOL doesn't say much, neither does Erland Sommarskog
This has bitten me in the past as well.
Not all errors generated inside the TRY block statements are passed into the CATCH block. Any errors with a severity of 10 or less are considered to be warnings and do not cause control to flow to the CATCH block. Also, any errors that break the database connection will not cause the CATCH block to be reached. There may be other situations as well.
Directly from http://msdn.microsoft.com/en-us/library/ms175976.aspx.
USE AdventureWorks2008R2;
GO
BEGIN TRY
-- Table does not exist; object name resolution
-- error not caught.
SELECT * FROM NonexistentTable;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH
The error is not caught and control passes out of the TRY…CATCH construct to the next higher level.
Running the SELECT statement inside a stored procedure will cause the error to occur at a level lower than the TRY block. The error will be handled by the TRY…CATCH construct.
This behaviour happens if you previously had a table IDontExist and compiled a plan for it that is still in the cache then drop the table.
It also happens if you run the individual statement twice even without the table ever existing. The first run raises an error that is not caught. The second run (after the first plan is cached) succeeds.
/*Clear Cache*/
DBCC FREEPROCCACHE
GO
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
/*Plan now Cached*/
SELECT query_plan
FROM sys.dm_exec_cached_plans cp
OUTER APPLY sys.dm_exec_sql_text(plan_handle) t
OUTER APPLY sys.dm_exec_query_plan(plan_handle) qp
WHERE t.text LIKE '%IDontExist%'
OPTION (RECOMPILE)
GO
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
The INSERT statement gets auto parameterised.
If you change your Select * from IDontExist statement to Select * from IDontExist WHERE ProductID = 1 this also becomes auto parameterised and they behave the same.
I'm not absolutely certain why the auto parameterisation makes a difference here. I think that it is explained by the below extract from BOL however.
The following types of errors are not handled by a CATCH block when they occur at the same level of execution as the TRY…CATCH construct ... [those] that occur during statement-level recompilation ... If an error occurs during compilation or statement-level recompilation at a lower execution level (for example, when executing sp_executesql or a user-defined stored procedure) inside the TRY block, the error
occurs at a lower level than the TRY…CATCH construct and will be handled by the associated CATCH block.
I presume the auto parametrization of that statement means that it gets recompiled at a lower execution level and is catchable.
Now that we have all the explanations as to why this is happening. Let's see an actual solution to the problem.
First let's take the statements that #d-k-mulligan proposed above and turn them into stored procs.
IF OBJECT_ID('dbo.prcIDontExistINSERT', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistINSERT
GO
CREATE PROCEDURE dbo.prcIDontExistINSERT
AS
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
IF OBJECT_ID('dbo.prcIDontExistSELECT', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistSELECT
GO
CREATE PROCEDURE dbo.prcIDontExistSELECT
AS
BEGIN TRY
SELECT * FROM IDontExist
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
If we run either of them we see the same error.
EXEC dbo.prcIDontExistINSERT
EXEC dbo.prcIDontExistSELECT
Msg 208, Level 16, State 1, Procedure prcIDontExistSELECT, Line 4
Invalid object name 'IDontExist'.
The solution now is to create error handling wrapper procs with the sole purpose of catching any error from the original procs above that are getting the object not found errors.
IF OBJECT_ID('dbo.prcIDontExistInsert_ERROR_HANDLER', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistInsert_ERROR_HANDLER
GO
CREATE PROCEDURE dbo.prcIDontExistInsert_ERROR_HANDLER
AS
BEGIN TRY
EXEC dbo.prcIDontExistINSERT
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
IF OBJECT_ID('dbo.prcIDontExistSELECT_ERROR_HANDLER', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistSELECT_ERROR_HANDLER
GO
CREATE PROCEDURE dbo.prcIDontExistSELECT_ERROR_HANDLER
AS
BEGIN TRY
EXEC dbo.prcIDontExistSELECT
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
Finally, let's run either of our error handling procs and see the message we expect.
EXEC dbo.prcIDontExistInsert_ERROR_HANDLER
EXEC dbo.prcIDontExistSELECT_ERROR_HANDLER
There was an error! Invalid object name 'IDontExist'.
NOTE: Kalman Toth did all the hard research work here:
http://www.sqlusa.com/articles2008/trycatch/
Workaround with dynamic sql. Maybe it will be helpful for someone.
begin try
exec('
insert into IDontExist(ProductID)
values(1)
')
end try
begin catch
select 'There was an error! ' + error_message()
end catch
Will Try-Catch capture all errors that ##ERROR can? In the following code fragment, is it worthwhile to check for ##ERROR? Will RETURN 1111 ever occur?
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
--do sql command here <<<<<<<<<<<
SELECT #Error=##ERROR
IF #Error!=0
BEGIN
IF XACT_STATE()!=0
BEGIN
ROLLBACK TRANSACTION
END
RETURN 1111
END
END TRY
BEGIN CATCH
IF XACT_STATE()!=0
BEGIN
ROLLBACK TRANSACTION
END
RETURN 2222
END CATCH
IF XACT_STATE()=1
BEGIN
COMMIT
END
RETURN 0
The following article is a must read by Erland Sommarskog, SQL Server MVP: Implementing Error Handling with Stored Procedures
Also note that Your TRY block may fail, and your CATCH block may be bypassed
One more thing: Stored procedures using old-style error handling and savepoints may not work as intended when they are used together with TRY … CATCH blocks.Avoid mixing old and new styles of error handling.
TRY/CATCH traps more. It's hugely and amazingly better.
DECLARE #foo int
SET #foo = 'bob' --batch aborting pre-SQL 2005
SELECT ##ERROR
GO
SELECT ##ERROR --detects 245. But not much use, really if the batch was a stored proc
GO
DECLARE #foo int
BEGIN TRY
SET #foo = 'bob'
SELECT ##ERROR
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE(), ERROR_NUMBER()
END CATCH
GO
Using TRY/CATCH in triggers also works. Trigger rollbacks used to be batch aborting too: no longer if TRY/CATCH is used in the trigger too.
Your example would be better if the BEGIN/ROLLBACK/COMMIT is inside, not outside, the construct
Try Catch will not trap everything
here is some code to demonstrate that
BEGIN TRY
BEGIN TRANSACTION TranA
DECLARE #cond INT;
SET #cond = 'A';
END TRY
BEGIN CATCH
PRINT 'a'
END CATCH;
COMMIT TRAN TranA
Server: Msg 3930, Level 16, State 1, Line 9
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Server: Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.
I don't believe control will ever reach the RETURN statement-- once you're in a TRY block, any error raised will transfer control to the CATCH block. However, there are some very serious errors that can cause the batch or even the connection itself to abort (Erland Sommarskog has written on the topic of errors in SQL Server here and here-- unfortunately, he hasn't updated them to include TRY...CATCH). I'm not sure if you can CATCH those kind of error, but then, ##ERROR is no good either.
It has been my experience that, as per Books Online, TRY...CATCH blocks will trap all events that would generate errors (and, thus, set ##ERROR to a non-zero value). I can think of no circumstances where this would not apply. So no, the return value would never be set to 1111, and it would not be worthwhile to include that ##Error check.
However, error handling can be very critical, and I'd hedge my bets for fringe situations such as DTC, linked servers, notification or brokerage services, and other SQL feature that I've had very little experience with. If you can, test your more bizarre situations to see what will actually happen.
The whole point of "Try..Catch" is so that you don't have to check for ##ERROR for every statement.
So it's not worthwhile.