I am using sql server 2012
This is my query:
begin try
select 1/0
end try
begin catch
select 'div by 0'
end catch
It returns 2 different results:
but the result expected is div by 0 ? because the control after the failure of try is transferred to catch?
So why does it return 2 results?
The significant thing here is that divison by zero is a runtime error and terminates the statement at the point it has reached. The statement select 1/0 is perfectly valid in itself and the resultset can be created, it's during the evaluation of the expression 1/0 for the first and only row of data that the error occurs. If you extend your example a little you will see this better;
begin try
declare #t table (i float)
insert #t values (2),(1),(0),(-1)
select 1/i as r from #t
end try
begin catch
select 'div by 0'
end catch
In the first resultset produced you will now see 2 rows, then the division by zero occurs and no further rows are produced. What is interesting to note though is the 'rows affected' messages.
(4 row(s) affected)
(0 row(s) affected)
(1 row(s) affected)
The 4 row(s) are from insert #t and the 1 row(s) is from the select 'div by 0'. The terminated select 1/i as r from #t reports 0 row(s), despite the fact that we can see 2 rows were returned to SSMS. What is happening here is that although the resultset is created and rows are returned to the client, the SQL Server database engine supports the ACID properties, and because the select 1/i as r from #t did not complete fully it is rolled back (the select is being atomic). SSMS may have received some rows, but as far as SQL Server is concerned no rows were produced. Extending your example again to capture the rows demonstrates this;
declare #t table (i float)
declare #t2 table (i float)
insert #t values (2),(1),(0),(-1)
begin try
insert #t2 select 1/i as r from #t
end try
begin catch
select 'div by 0'
end catch
select * from #t2
When this is run you will see that #t2 contains no rows.
If you want to read up further on SQL Server's behaviour when division by zero occurs see ARITHABORT, ARITHIGNORE and ANSI_WARNINGS in Erland Sommarskog's error handling document.
Yes. It will show two results. It can be solved in two ways
1. Using with stored procedure
If you want to show result based on TRY and CATCH, try using an OUT parameter for returning result
CREATE PROCEDURE ProcedureName
#RESULT VARCHAR(100) OUTPUT
AS
BEGIN
begin try
select #RESULT = 1/0
end try
begin catch
select #RESULT = 'div by 0'
end catch
select #RESULT
END
2. Using without stored procedure
Declare a variable that holds the result based on TRY and CATCH
DECLARE #RESULT VARCHAR(100)
begin try
select #RESULT = 4/0
end try
begin catch
select #RESULT = 'div by 0'
end catch
select #RESULT
Related
I am fairly new at writing procedures (beyond the basics)
I am trying to write a stored procedure that inserts into a table (dbo.billing_batch) based on a select statement that loops through the list of results (#DealerID FROM dbo.vehicle_info).
The SELECT DISTINCT... statement on its own works perfectly and returns a list of 54 records.
The result of the SELECT statement is dynamic and will change from week to week, so I cannot count on 54 records each time.
I am trying to use WHILE #DealerID IS NOT NULL to loop through the INSERT routine.
The loop is supposed to update dbo.billing_batch, however it is inserting the same 1st record (BillingBatchRosterID, DealerID) over and over and over to infinity.
I know I must be doing something wrong (I have never written a stored procedure that loops).
Any help would be greatly appreciated!
Here is the stored procedure code:
ALTER PROCEDURE [dbo].[sp_billing_batch_set]
#varBillingBatchRosterID int
AS
SET NOCOUNT ON;
BEGIN
DECLARE #DealerID int
SELECT DISTINCT #DealerID = vi.DealerID
FROM dbo.vehicle_info vi
LEFT JOIN dbo.dealer_info di ON di.DealerID = vi.DealerID
WHERE di.DealerActive = 1
AND (vi.ItemStatusID < 4 OR vi.ItemStatusID = 5 OR vi.ItemStatusID = 8)
END
WHILE #DealerID IS NOT NULL
BEGIN TRY
INSERT INTO dbo.billing_batch (BillingBatchRosterID, DealerID)
VALUES(#varBillingBatchRosterID, -- BillingBatchRosterID - int
#DealerID) -- DealerID - int
END TRY
BEGIN CATCH
SELECT ' There was an error: ' + error_message() AS ErrorDescription
END CATCH
You have the same problems as another recent post here: Iterate over a table with a non-int id value
Why do a loop? Just do it as a single SQL statement
If you must use a loop, you will need to update your #Dealer value at each run (e.g., to the next DealerId) otherwise it will just infinitely loop with the same DealerID value
Don't do a loop.
Here's an example not needing a loop.
ALTER PROCEDURE [dbo].[P_billing_batch_set]
#varBillingBatchRosterID int
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
INSERT INTO dbo.billing_batch (DealerID, BillingBatchRosterID)
SELECT DISTINCT vi.DealerID, #varBillingBatchRosterID
FROM dbo.vehicle_info vi
INNER JOIN dbo.dealer_info di ON di.DealerID = vi.DealerID
WHERE di.DealerActive = 1
AND (vi.ItemStatusID < 4
OR vi.ItemStatusID = 5
OR vi.ItemStatusID = 8
);
END TRY
BEGIN CATCH
SELECT ' There was an error: ' + error_message() AS ErrorDescription;
END CATCH;
END;
Note I
Changed the LEFT JOIN to an INNER JOIN as your WHERE clause needs the record to exist in the dealer_info table
Moved the SET NOCOUNT ON; to be within the BEGIN-END section
Moved the END to the end
Renamed your stored procedure as per the excellent comment from #marc_s (on the question itself)
I need to return a resultset consisting of database errors from a SQL Server stored procedure's CATCH clause but I'm stuck with it. Do I need to use cursors to return resultset and if so, then what is the type declaration for the OUTPUT parameter in my .NET application? I tried Object and Variant but did not work.
I also tried the simple way just using a SELECT statement to return and it works with one stored procedure but not with another as thus in my CATCH clause:
while (#I <= #count)
begin
BEGIN TRY
-- delete all previous rows inserted in #customerRow for previous counts #I
delete from #customerRow
-- this is inserting the current row that we want to save in database
insert into #customerRow
SELECT
[id],[firstname], [lastname], [street], [city],
[phone],[mobile],[fax], [email], [companyName],
[licence],[brn], [vat], [companyStreet], [companyCity], [status]
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS rownumber,
[id], [firstname], [lastname], [street], [city],
[phone], [mobile], [fax], [email], [companyName],
[licence], [brn], [vat], [companyStreet], [companyCity], [status]
FROM
#registerDetails) AS foo
WHERE
rownumber = #I
-- this stored procedure handles the saving of the current customer row just defined above
-- if there is any error from that sproc, it will jump to CATCH block
--save the error message in the temp table and continue
--with the next customer row in the while loop.
exec dbo.sp_SaveCustomer #customerRow
END TRY
BEGIN CATCH
IF ##TranCount = 0
-- Transaction started in procedure.
-- Roll back complete transaction.
ROLLBACK TRANSACTION;
if XACT_STATE()= -1 rollback transaction
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT #ErrorMessage = ERROR_MESSAGE() + ' ' + (select firstname from #registerDetails where id=#I)
SELECT #ErrorSeverity = ERROR_SEVERITY();
SELECT #ErrorState = ERROR_STATE()
INSERT INTO #registrationResults (error,id)
SELECT #ErrorMessage, #I
END CATCH
set #I = #I +1
end
COMMIT TRANSACTION registerTran
select * from #registrationResults
The above works with one stored procedure when I call it in my vb.net code as :
ta.Fill(registrationErrors, clientDetailsDT)
where registrationErrors and clientDetailsDT are strongly typed data tables.
This one does not :
begin catch
IF ##TranCount > 0 or XACT_STATE()= -1 ROLLBACK TRANSACTION;
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
DECLARE #ErrorLine INT;
SELECT #ErrorMessage = ERROR_MESSAGE();
SELECT #ErrorSeverity = ERROR_SEVERITY();
SELECT #ErrorState = ERROR_STATE();
SELECT #ErrorLine = ERROR_Line();
****ERROR -- THIS SELECT WAS MESSING ALL UP as it was this select that was being returned to the .NET and not the select of the desired #temp table after, hence returning 0 resultset as this select was EMPTY. !!
select status_indicator from InsertInvoiceTriggerData where session_GUID = guid**
delete from InsertInvoiceTriggerData where session_GUID = #guid**
INSERT INTO #registrationResults (error,id)
SELECT #ErrorMessage, NULL
select * from #registrationResults
end catch
Any suggestions how to return resultsets?
I haven't seen your database code, but in my experience the very first error caught by catch means that the entire transaction has to be rolled back. Apart from other things, it also implies that I never have more than 1 error to return in any given situation.
As such, I use 2 scalar output parameters in my stored procedures, that is:
#Error int = null output,
#Message nvarchar(2048) = null output
And I can retrieve them just like any other output variables.
UPD: Even after you have added some code, I still fail to understand what is your problem, exactly. However, I see several problems with your code, so I'll point them out and chances are, one of them will solve the problem. I am commenting only the first snippet, since the last one is too incomplete.
You should have been started the outermost transaction somewhere before the loop. If not, the code will fail.
If I guessed correctly, you implemented all savepoint logic inside the dbo.sp_SaveCustomer stored proc. If not, the whole discussion is pointless, since there are no save tran or rollback #savepoint statements in the code you have shown.
The first catch statement - IF ##TranCount = 0 ROLLBACK TRANSACTION is all wrong. If the condition is successful, it will result in error trying to rollback nonexistent transaction. Should not be here if you rely on savepoints.
The next after it should result in unconditional break:
if XACT_STATE()= -1 begin
rollback transaction;
break;
end;
The rest of your catch code can be replaced with this:
INSERT INTO #registrationResults (error, id)
SELECT error_message() + ' ' + firstname, id
from #registerDetails where id=#I;
Also, never use temp tables for this purpose, because rollback will affect them as well. Always use table variables for this, they are non-transactional (just like any other variable).
The commit should be conditional, because you may end up at this point with no transaction to commit:
if ##trancount > 0 commit tran;
There is no point in specifying savepoint name in the commit statement, it only leads to confusion (though isn't considered an error). Also, there should not any savepoint in this module (unless you have defined it before the loop).
I suspect that's just the tip of the iceberg, since I have no idea what actually happens inside the dbo.SaveCustomer stored procedure.
UPD2: Here is a sample of my VB.NET code which I use to receive recordsets from stored procedures:
Private Function SearchObjectsBase( _
SearchMode As penum_SEARCH_MODE, SearchCriteria As String
) As DataSet
Dim Cmd As DbCommand, Pr As DbParameter, dda As DbDataAdapter
' Initialise returning dataset object
SearchObjectsBase = New DataSet()
Cmd = MyBase.CreateCommand(String.Format("dbo.{0}", SearchMode))
With Cmd
' Parameter definitions
Pr = .CreateParameter()
With Pr
.ParameterName = "#SearchCriteria"
.DbType = DbType.Xml
.Value = SearchCriteria
End With
.Parameters.Add(Pr)
' Create data adapter to use its Fill() method
dda = DbProviderFactories.GetFactory(.Connection).CreateDataAdapter()
' Assign the prepared DbCommand as a select method for the adapter
dda.SelectCommand = Cmd
' A single resultset is expected here
dda.Fill(SearchObjectsBase)
End With
' Set error vars and get rid of it
Call MyBase.SetErrorOutput(Cmd)
' Check for errors and, if any, discard the dataset
If MyBase.ErrorNumber <> 0 Then SearchObjectsBase.Clear()
End Function
I use .NET 4.5, which has a very nice method to automatically select the most appropriate data adapter based on the actual connection.
And here is a call of this function:
Dim XDoc As New XElement("Criteria"), DS As DataSet = Nothing, DT As DataTable
...
DS = .SearchPatients(XDoc.ToString(SaveOptions.None))
' Assign datasource to a grid
Me.dgr_Search.DataSource = DS.Tables.Item(0)
Here, SearchPatients() is a wrapper on top of the SearchObjectsBase().
Curious if anyone has experienced this problem before and what the root cause is. The problem is that an error occurs when executed in SQL 2012 with Include Actual Query Plan turned on. It works in 2008 R2 either way (with or without plan) and works in 2012 without the Plan turned on. I discovered this when testing out functionality against partitioned views.
--Setup
USE master
go
DROP DATABASE Test
GO
CREATE DATABASE Test
GO
USE Test
GO
CREATE TABLE DD (pkID int IDENTITY(1,1), FullDate date);
INSERT INTO DD (FullDate) VALUES ('2013-01-01')
INSERT INTO DD (FullDate) VALUES ('2013-01-02')
INSERT INTO DD (FullDate) VALUES ('2013-01-03')
INSERT INTO DD (FullDate) VALUES ('2013-01-04')
INSERT INTO DD (FullDate) VALUES ('2013-01-05')
GO
CREATE TABLE DC (pkID int IDENTITY(1,1), Filter varchar(32), FilterGroup varchar(32));
INSERT INTO DC (Filter, FilterGroup) VALUES ('one', 'groupone')
INSERT INTO DC (Filter, FilterGroup) VALUES ('two', 'grouptwo')
INSERT INTO DC (Filter, FilterGroup) VALUES ('three', 'groupone')
GO
CREATE TABLE FDA1 (pkID int IDENTITY(1,1), fkpID int, fkCID int, fkDateID int)
INSERT INTO FDA1(fkpID, fkCID, fkDateID) VALUES (1,1,1)
INSERT INTO FDA1(fkpID, fkCID, fkDateID) VALUES (1,2,1)
INSERT INTO FDA1(fkpID, fkCID, fkDateID) VALUES (1,1,3)
INSERT INTO FDA1(fkpID, fkCID, fkDateID) VALUES (1,3,5)
GO
CREATE TABLE FDA2 (pkID int IDENTITY(1,1), fkpID int, fkCID int, fkDateID int)
INSERT INTO FDA2(fkpID, fkCID, fkDateID) VALUES (2,1,2)
INSERT INTO FDA2(fkpID, fkCID, fkDateID) VALUES (2,2,2)
INSERT INTO FDA2(fkpID, fkCID, fkDateID) VALUES (2,1,4)
INSERT INTO FDA2(fkpID, fkCID, fkDateID) VALUES (2,3,5)
GO
CREATE VIEW FDA
AS
SELECT pkID, fkpID, fkCID, fkDateID FROM FDA1
UNION ALL
SELECT pkID, fkpID, fkCID, fkDateID FROM FDA2
GO
CREATE FUNCTION GetFilter
(
#pID int
, #filterGroup varchar(32)
)
RETURNS #Filter TABLE
(
CID int
)
AS
BEGIN
INSERT INTO
#Filter
SELECT
dc.pkID
FROM
DC dc
WHERE
dc.FilterGroup = #filterGroup
RETURN
END
GO
CREATE PROC test (#ID int)
AS
BEGIN
BEGIN TRY
DECLARE #FilterGroup varchar(32) = 'groupone'
SELECT
CAST(MIN(dd.FullDate) As datetime) as ProjectReviewStartDate
FROM
dbo.FDA fda
INNER JOIN dbo.DD dd On fda.fkDateID = dd.pkID
INNER JOIN dbo.GetFilter(#ID, #FilterGroup) ctl on fda.fkCID = ctl.CID
WHERE
fda.pkID = #ID
OPTION (RECOMPILE);
RETURN 0;
END TRY
BEGIN CATCH
--Declare variables for error information.
Declare
#ErrorMessage nvarchar(max),
#ErrorSeverity bigint,
#ErrorState int;
--Populate error information.
Select
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
--Re-throw error to calling method.
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState);
--Failure
RETURN -1;
END CATCH;
END
GO
Now that setup is done, let's run the actual code, first "Include Actual Execution Plan"
USE Test
GO
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO
DECLARE #id int = 1
DECLARE #tbl table (dt datetime)
DECLARE #findate datetime
INSERT #tbl EXEC test #id
SELECT #findate = dt FROM #tbl
SELECT #findate
GO
You should receive the result of: 2013-01-01 00:00:00.000.
Now turn on Include Actual Execution Plan and you will receive the result of NULL and see an error (In SQL 2012):
Msg 50000, Level 16, State 10, Procedure test, Line 33
String or binary data would be truncated.
With Include Actual Execution Plan on and removing the Try/Catch block from the proc removes the error and returns the correct result. However again this works fine in 2008R2.
Any ideas?
Thanks
I don't have an answer for you yet, just more ammo that you have likely encountered a bug.
First, to prove that this doesn't involve RAISERROR specifically, changing that line to:
THROW;
Returns this error message instead:
Msg 8152, Level 16, State 10, Procedure test, Line 7
String or binary data would be truncated.
So this shows that the error is happening in the SELECT call and has something to do with the combination of TRY/CATCH and show actual plan.
Next, let's see what the metadata describes as the output for the stored procedure:
SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set_for_object
(OBJECT_ID('test'), NULL);
Results:
name system_type_name
---------------------- ----------------
ProjectReviewStartDate datetime
Now let's see what it says about the metadata (well, actually, turns out an error message) if we try to test the metadata for the batch you're running:
SELECT [error_message]
FROM sys.dm_exec_describe_first_result_set(N'DECLARE #id int = 1
DECLARE #tbl table (dt datetime)
DECLARE #findate datetime
INSERT #tbl
EXEC test #id', -1, NULL);
The result:
error_message
----------------------------------------------------------
Incorrect syntax near '-'.
The batch could not be analyzed because of compile errors.
There is no - in the batch. And this is with execution plan turned off. And you get the same result even if you comment out the insert in the batch above. Even for example:
SELECT error_message
FROM sys.dm_exec_describe_first_result_set(N'EXEC test 1', -1, NULL);
The error message changes ever so slightly (look closely):
error_message
----------------------------------------------------------
Incorrect syntax near '1'.
The batch could not be analyzed because of compile errors.
We've removed any involvement of showplan or potential truncation and we still can demonstrate that SQL Server has some kind of problem compiling and/or generating metadata for this simple batch.
Seems to be a pretty simple repro, and you have workarounds, but I would definitely file this on Connect.
edit
I see that you have filed a bug; anyone else who can repro this situation should vote and confirm repro:
http://connect.microsoft.com/SQLServer/feedback/details/785151/show-actual-query-plan-causes-error
I'm using the following code in sql server 2005.
BEGIN TRANSACTION;
CREATE TABLE dbo.MyTable
(
idLang int NOT NULL IDENTITY (1, 1),
codeLang nvarchar(4) NOT NULL
) ON [PRIMARY];
IF ##ERROR = 0
BEGIN
PRINT 'before_commit';
COMMIT TRANSACTION;
PRINT 'after_commit';
END
ELSE
BEGIN
PRINT 'before_rollback';
ROLLBACK TRANSACTION;
PRINT 'after_rollback';
END
GO
1 - Display when MyTable doesn't exist (no error case) :
before_commit
after_commit
=> OK
2 - Display when MyTable exists (error case) :
'There is already an object named 'MyTable' in the database.'
=> Why the "else" statement is not executed ? (no print, no rollback)
I know the alternative with try-catch but i'd like to understand this strange case...
Thanks !
The CREATE TABLE will be checked during query compilation and fail, so none of the code in the batch is executed. Try adding:
SELECT ##TRANCOUNT
To the end of the script (i.e. after the GO), and you'll see the BEGIN TRANSACTION never occurred either.
I can't say specifically why your problem is occurring. Personally, I'm not sure I would use a transaction and error handling or a try/catch block to do this.
Have you tried querying the sys.tables table instead to check for its existence. Something of this ilk:
IF EXISTS(SELECT * FROM sys.tables WHERE object_id = object_id('MyTable'))
BEGIN
print 'table already exists'
END
ELSE
BEGIN
CREATE TABLE dbo.MyTable
(
idLang int NOT NULL IDENTITY (1, 1),
codeLang nvarchar(4) NOT NULL
) ON [PRIMARY];
END
I'm having a stored procedure which returns two result sets based on the success or failure.
SP success result set: name, id ,error,desc
SP failure result sret: error,desc
I'm using the following query to get the result of the stored procedure. It returns 0 for success and -1 for failure.
declare #ret int
DECLARE #tmp TABLE (
name char(70),
id int,
error char(2),
desc varchar(30)
)
insert into #tmp
EXEC #ret = sptest '100','King'
select #ret
select * from #tmp
If the SP is success the four field gets inserted into the temp table since the column matches.
But in case of failure the sp result set has only error and desc which does not matchs with no of columns in the temp table...
.I can't change the Sp, so I need to do some thing (not sure) in temp table to handle both failure and success.
You can't return 2 different recordsets and load the same temp table.
Neither can try and fill 2 different tables.
There are 2 options.
Modify your stored proc
All 4 columns are returned in all conditions
1st pair (name, ID) columns are NULL on error
2nd pair (error, desc) are NULL on success
If you are using SQL Server 2005 then use the TRY/CATCH to separate your success and fail code paths. The code below relies on using the new error handling to pass back the error result set via exception/RAISERROR.
Example:
CREATE PROC sptest
AS
DECLARE #errmsg varchar(2000)
BEGIN TRY
do stuff
SELECT col1, col2, col3, col4 FROM table etc
--do more stuff
END TRY
BEGIN CATCH
SELECT #errmsg = ERROR_MESSAGE()
RAISERROR ('Oops! %s', 16, 1, #errmsg)
END CATCH
GO
DECLARE #tmp TABLE ( name CHAR(70), id INT, error char(2), desc varchar(30)
BEGIN TRY
insert into #tmp
EXEC sptest '100','King'
select * from #tmp
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
END CATCH
My fault!!
Was too quick in the answer.
You need only to relv on the return value, so building up the logic against it is much better.
If you still want to use the temp table, then calling the sptest twice could be a way to deal with it (not optimal though), one time to get the return value and based on it then have 2 different temp tables you are filling up (one would be with the 4 fields, the other only with 2 fields).
declare #ret int
DECLARE #tmp TABLE (name CHAR(70), id INT, error char(2), desc varchar(30))
DECLARE #tmperror TABLE (error char(2), desc varchar(30))
EXEC #ret = sptest '100','King'
IF #ret != 0
BEGIN
INSERT INTO #tmperror
EXEC sptest '100','King';
SELECT * FROM #tmperror;
END
ELSE
BEGIN
INSERT INTO #tmp
EXEC sptest '100','King';
SELECT * FROM #tmp;
END
Keep in mind that this solution is not optimal.
Try modifying your table definition so that the first two columns are nullable:
DECLARE #tmp TABLE (
name char(70) null,
id int null,
error char(2),
desc varchar(30)
)
Hope this helps,
Bill
You cannot do this with just one call. You will have to call it once, either getting the return status and then branching depending on the status to the INSERT..EXEC command that will work for the number of columns that will be returned or Call it once, assuming success, with TRY..CATCH, and then in the Catch call it again assuming that it will fail (which is how it got to the CATCH).
Even better, would be to either re-write the stored procedure so that it returns a consistent column set or to write you own stored procedure, table-valued function or query, by extracting the code from this stored procedure and adapting it to your use. This is the proper answer in SQL.