We are handling Exceptions in our stored procedures using Try/Catch block. In a stored procedure I have intentionally made an error to check how the exceptions being handled. I wrote a stored procedure like as below,
CREATE PROCEDURE TEST
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRAN
;WITH CTE
AS
(SELECT TOP 10 *
FROM student)
SELECT * FROM CTE
SELECT * INTO #VAL
FROM CTE
IF ##ERROR = 0
BEGIN
COMMIT TRAN;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR, ERROR_LINE() AS [Error Line], ERROR_MESSAGE() AS [Error Message]
ROLLBACK TRAN;
END CATCH
SET NOCOUNT OFF;
END
In the Above Stored procedure I have used a CTE multiple Times. I expected that SQL-SERVER will handle the exception but it didn't. While Executing the Above stored Procedure I got below error
Msg 208, Level 16, State 1, Procedure TEST, Line 16
Invalid object name 'CTE'.
Msg 266, Level 16, State 2, Procedure TEST, Line 16
Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 1, current count = 2.
Why the Exception is not handled ? Could someone give idea on this?
Thanks for the help.
stored procedures follow deferred object name resolution while executing.So in this case cte is a non existent object to it .
Further try catch cannot handle this type of errors
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:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as **object name resolution errors** that occur after compilation because of deferred name resolution
Please see MSDN for more info(see errors unaffected by try catch )
Related
I wrote a very simple procedure. I am deliberately making a mistake in the procedure. but the error is not working. I want to return the error I want with raiserror. but it doesn't even go inside the "if".
ALTER PROCEDURE dene
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO bddksektor ([tcmbkodu], [aktif], [bddksektorkodu])
VALUES ('a', 1, 1)
-- I knowingly made a mistake. normally the 1st parameter is int.
-- I'm entering varchar so that it can enter "if" and return an error. but it doesn't
IF ##ERROR <> 0
BEGIN
RAISERROR('Hata', 16, 1, 61106)
RETURN 61106
END
ELSE
BEGIN
SELECT
[tcmbkodu], [aktif], [bddksektorkodu]
FROM
[dbfactoringtest].[dbo].[bddksektor]
ORDER BY
tcmbkodu ASC
END
SET NOCOUNT OFF
END
SQL Server returns its own error when I run the procedure. It does not return the error I wrote because it does not enter the "if"
Error:
Msg 245, Level 16, State 1, Procedure dene, Line 11 [Batch Start Line 1]
Conversion failed when converting the varchar value 'a' to data type int
As I mention in the comment, use a TRY...CATCH. For your SQL if the first INSERT fails the batch will be aborted, and so the IF won't be entered, because it won't be reached. I also recommend switching to THROW instead of RAISERROR as noted in the documentation:
Note
The RAISERROR statement does not honor SET XACT_ABORT. New applications should use THROW instead of RAISERROR.
This gives you something like this:
CREATE OR ALTER PROCEDURE dbo.dene AS --Always schema qualify
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
INSERT INTO dbo.bddksektor ([tcmbkodu],[aktif],[bddksektorkodu]) --Always schema qualify
VALUES ('a',1,1);
SELECT [tcmbkodu]
,[aktif]
,[bddksektorkodu]
FROM [dbfactoringtest].[dbo].[bddksektor] --Why is this using 3 part naming? Is this copy of bddksektor in a different database?
ORDER BY tcmbkodu ASC;
END TRY
BEGIN CATCH
THROW 61106, N'Hata', 16;
RETURN 61106;
END CATCH;
SET NOCOUNT OFF;
END
Question
The documentation of SET XACT_ABORT says little more than this about the effect of enabling this option.
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a run-time error, the entire transaction is terminated and rolled back.
I do not believe this is the full truth. After reading this, I was worried that if a stored procedure which enables this option is executed in a transaction created by an external process, it may end up rolling back the outer transaction. Luckily, my fears turned out to be unfounded. However, this means now that I do no truly understand how XACT_ABORT works. What are the conditions SQL Server checks for whether a transaction should be rolled back or not?
Prior investigation
I have carried out the following experiment: (a summary of this code is below, as having a numbered list before a code block breaks StackOverflow's formatting, duh)
CREATE TABLE Dummy
(
ID INT NOT NULL IDENTITY CONSTRAINT PK_Dummy PRIMARY KEY,
Text NVARCHAR(128) NOT NULL
)
CREATE UNIQUE NONCLUSTERED INDEX IX_Dummy_Text ON dbo.Dummy(Text)
GO
CREATE OR ALTER PROCEDURE InsertDummy
#Text NVARCHAR(128)
AS
BEGIN
SET NOCOUNT OFF
SET XACT_ABORT ON
INSERT dbo.Dummy (Text) VALUES (#Text)
END
GO
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
EXEC dbo.InsertDummy #Text = N'Dummy'
EXEC dbo.InsertDummy #Text = N'Dummy' --DUPLICATE!
END TRY
BEGIN CATCH
PRINT 'ERROR! ##TRANCOUNT is ' + CONVERT(NVARCHAR, ##TRANCOUNT)
-- Echo the error
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT #ErrorMessage = ERROR_MESSAGE();
SELECT #ErrorSeverity = ERROR_SEVERITY();
SELECT #ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH
PRINT 'At the end ##TRANCOUNT is ' + CONVERT(NVARCHAR, ##TRANCOUNT)
IF ##TRANCOUNT>0
ROLLBACK
Create a Dummy table with a UNIQUE index
A stored procedure which inserts into Dummy. The procedures enables XACT_ABORT.
Code which executes this procedure twice, in a transaction. The second call fails, as it attempts to insert a duplicate value into Dummy.
The same code prints out the ##TRANCOUNT value to show if we are still in a transaction or not. It also enables XACT_ABORT.
The output of this test is:
(1 row affected)
(0 rows affected)
ERROR! ##TRANCOUNT is 1
Msg 50000, Level 14, State 1, Line 74
Cannot insert duplicate key row in object 'dbo.Dummy' with unique index 'IX_Dummy_Text'. The duplicate key value is (Dummy).
At the end ##TRANCOUNT is 1
An error was raised yet the the transaction not rolled back. The way this setting works is clearly not as simplistic as the documentation would have me believe. Why was the transaction not rolled back?
This answer mentions that XACT_ABORT only rolls back the transaction if the severity of the error is at least 16. The error in this example is only level 14. However, even if I replace the INSERT in the procedure with RAISERROR (N'Custom error', 16, 0), the transaction is still not rolled back.
UPDATE: What I found that although the transaction is not rolled back in my test, it is doomed! ##TRANCOUNT is 1 when I execute this sample regardless of the XACT_ABORT setting: but if the setting is ON, XACT_STATE() is -1, indicating an uncomittable transaction. When XACT_ABORT is OFF, XACT_STATE() is 1.
Question
"An error was raised yet the transaction not rolled back. The way these setting works is clearly not as simplistic as the documentation would have me believe. Why was the transaction not rolled back"
The answer to that is that RAISERROR will not cause XACT_ABORT to
trigger! This means we can be in a very messed up state transaction
wise
Abort, Abort, We Are XACT_ABORT:ing, Or Are We?!
According to MSDN ,
The THROW statement honors SET XACT_ABORT. RAISERROR does not. New
applications should use THROW instead of RAISERROR.
We can use the THROW statement instead of the RAISERROR.
So we can use the following statement in order to trigger the XACT_ABORT
TRUNCATE TABLE Dummy
GO
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
EXEC dbo.InsertDummy #Text = N'Dummy'
EXEC dbo.InsertDummy #Text = N'Dummy' --DUPLICATE!
END TRY
BEGIN CATCH
THROW
END CATCH
PRINT 'At the end ##TRANCOUNT is ' + CONVERT(NVARCHAR, ##TRANCOUNT)
IF ##TRANCOUNT>0
ROLLBACK
The output will be;
(1 row affected)
(0 rows affected)
Msg 2601, Level 14, State 1, Procedure dbo.InsertDummy, Line 7 [Batch Start Line 5]
Cannot insert duplicate key row in object 'dbo.Dummy' with unique index 'IX_Dummy_Text'. The duplicate key value is (Dummy).
For the updated issue you can see set xact_abort on and try-catch together
I am using below SQL to delete records and insert into Customers table inside a transaction. If there is an error in the insert statements, I am seeing the error message and when I try to execute select * from customers, it is not displaying result set. And when I close SSMS window, it is showing There are uncommitted transactions. Do you wish to commit these transactions before closing the window?
After I click OK, results are getting displayed from the table. So, is there any locking mechanism taking place while using transaction.
USE CMSDB;
BEGIN TRY
BEGIN TRAN t1;
DELETE FROM Customers
print ##trancount -->prints 3 since there are three records
INSERT INTO CUSTOMERS
INSERT INTO CUSTOMERd --> error here
INSERT INTO CUSTOMERS
COMMIT TRAN t1;
END TRY
BEGIN CATCH
print 'hi' --> not printing
select ##trancount --> not resulting anything
IF ##TRANCOUNT > 0
ROLLBACK TRAN t1;
-- Error Message
DECLARE #Err nvarchar(1000)
SET #Err = ERROR_MESSAGE()
RAISERROR (#Err,16,1)
END CATCH
GO
Message
(3 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
Msg 208, Level 16, State 1, Line 8
Invalid object name 'dbo.Customerd'.
Excerpt from TRY…CATCH description:
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:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of
deferred name resolution.
In this case what happens is
The error is not caught and control passes out of the TRY…CATCH
construct to the next higher level.
In SQL SERVER 2008 how can i return error messages as select statement
LIKE
SELECT ** FROM emp
Will return the following error
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '*'.
i just need the 2nd line to be returned as SELECT statement
I've tried ##ERROR but looks like it return just the error code
What i'm doing is validating sql statements from the client side, so if there is a way of doing this with out hitting the server this will be good too i'm using VB
Thanks
You do see Incorrect syntax near '*'., don't you? That means it was returned!
You may want to know how to retrieve the error message(s) text, and the answer is that you can only capture error that occur during execution (syntax errors are compilation and cannot be captured inside the same batch) and you must use the BEGIN ... TRY/ BEGIN ... CATCH block. Inside a catch block the ERROR_MESSAGE() function will return the text of the exception caught.
So, to give an example base don your case, wrap the code in a BEGIN TRY/BEGIN CATCH and have the incorrect syntax in a different batch:
begin try
exec sp_executesql N'SELECT ** FROM emp';
SELECT NULL;
end try
begin catch
select ERROR_MESSAGE();
end catch
You can use error_message()
but your error looks like compile error, so it will not help you. You can catch it only if you'll use dynamic SQL :)
Just to test it
begin try
exec sp_executesql
#stmt = N'SELECT ** FROM emp'
end try
begin catch
select error_message()
end catch
Also you can take a look at error_procedure(), error_line(), error_state() and so on
UPDATE If you're trying to handle server errors at your client, I suggest you to read more about VB exceptions.
Try this link http://msdn.microsoft.com/en-us/library/aa289505(v=vs.71).aspx
I have a stored procedure for select data between many different sql server, and all the sql server have set the link server at the sql server where the stored procedure built in.
Here is my procedure:
Create Proc dbo.spGetData
#code as char(4)
AS
if (#code='aaaa')
Select date From [ServerA].Stock.dbo.Syspara
else if (#code='bbbb')
Select date From [ServerB].Stock.dbo.Syspara
else if (#code='cccc')
Select date From [ServerC].Stock.dbo.Syspara
else if (#code='dddd')
Select date From [ServerD].Stock.dbo.Syspara
GO
If the [ServerB] isn't alive or is closed, when I call:
exec dbo.spGetData 'dddd'
There will be an error, but if the all 4 server are alive, the query will return without error.
How can I do to avoid the problem?
Add TRY..CATCH error handling:
The following example shows how an object name resolution error generated by a SELECT statement is not caught by the TRY…CATCH construct, but is caught by the CATCH block when the same SELECT statement is executed inside a stored procedure.
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.
-- Verify that the stored procedure does not exist.
IF OBJECT_ID ( N'usp_ExampleProc', N'P' ) IS NOT NULL
DROP PROCEDURE usp_ExampleProc;
GO
-- Create a stored procedure that will cause an
-- object resolution error.
CREATE PROCEDURE usp_ExampleProc
AS
SELECT * FROM NonexistentTable;
GO
BEGIN TRY
EXECUTE usp_ExampleProc
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() as ErrorNumber,
ERROR_MESSAGE() as ErrorMessage;
END CATCH;
If, like me, you still have to use SQL Server 2000, then you can't use try catch blocks.
I don't think it will help with the time out, but if you break your if statements out into individual statements and check ##ERROR after each one, that will get get you better control.