T-SQL finding out which row causes the error - sql-server

I have a stored procedure that inserts thousands of rows and if there is bad data in any of the rows, it fails. I do the inserts inside a while loop and it would be nice if there is a way I can have it spit out the row at which the stored procedure is failing. The query and the loop is as such:
WHILE #rownum > 1
BEGIN
INSERT INTO dbo.bestsellers (id, name, datastring, country)
VALUES (#id, #name, #datastring, #country)
END
Is there a way to have it spit it out the #id, #name, #datastring, #country data at which it is failing due to bad data?

You can use a TRY CATCH structure, and in the CATCH block, raise a custom error message with all the values of your variables and the #rownum in it.

Try using Try Catch Block and also log the information in the Catch Block.
Refer this link for sample:
https://msdn.microsoft.com/en-us/library/ms175976.aspx
BEGIN TRY
{ sql_statement | statement_block }
END TRY
BEGIN CATCH
[ { sql_statement | statement_block } ]
END CATCH
[ ; ]

The following try catch block should work:
BEGIN TRY
WHILE #rownum > 1
BEGIN
INSERT INTO dbo.bestsellers (id, name, datastring, country)
VALUES (#id, #name, #datastring, #country)
END
END TRY
BEGIN CATCH
PRINT '================================================';
PRINT 'ERROR: #rownum=' + cast(#rownum as varchar) + ' #name=' + #name + ' #datastring=' + #datastring + ' #country=' + #country;
PRINT '================================================';
THROW
END CATCH;

Related

Throw error on TRY, in order to manage all error instruction in the CATCH block

I'm writing a Stored Procedure to do "lots of things" (input validation, insert and update statements, transaction and cursors management and so on).
I'd like to use TRY/CATCH in order to manage
transaction rollback
cursor closing
error printing
in a single code block (I don't like to repeat ROLLBACK TRANSACTION T1 every time i check something which may let the work be rolled back...).
For example:
BEGIN TRY
BEGIN TRANSACTION T1
SET #val1 = (
SELECT val1
FROM tab1
WHERE cond1 = 'cond1'
)
IF (ISNULL(#val1, '') = '')
BEGIN
-- insert the throw of an error
END
-- other code...
COMMIT TRANSACTION T1
END TRY
BEGIN CATCH
-- error handling code, included the error thrown in the IF above
ROLLBACK TRANSACTION T1
RETURN
END CATCH
Is it possible to throw an error in the BEGIN/END of the IF statement to go into the BEGIN CATCH so that all errors are handled in a single block, even those raised explicitly by me?
Try something like the below.
BEGIN TRY
BEGIN TRANSACTION T1
SELECT 1;
SELECT 2;
SELECT 3;
RAISERROR('Here is my error!', 16, 1);
SELECT 4;
SELECT 5;
COMMIT TRANSACTION T1
END TRY
BEGIN CATCH
DECLARE #x VARCHAR(1000);
SELECT #x = ERROR_MESsAGE();
print #x;
IF (XACT_STATE() <> 0) ROLLBACK TRAN;
-- RETURN
END CATCH

USING LEN To test for data validation

Using a stored procedure that receives an input parameter for the Vendor state. I'm trying to cause a custom error to be thrown when the state VARCHAR value is more than 2 chars in length. I'm using the LEN function to count the parameter length and to compare the value passed to the integer 2. Logically this looks right but the error is not being thrown. Any help?
USE AP
GO
CREATE PROC TEST123
#state VARCHAR(2) = NULL
AS
IF LEN(#state) <= 2
SELECT TOP 1 VendorName
FROM Vendors
WHERE VendorState = #state;
ELSE
THROW 50001, 'Invalid state length', 1;
GO
BEGIN TRY
USE AP
EXEC TEST123 #state = 'CAA';
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
END CATCH
Use RAISERROR in block of else statement and THROW in the catch block.
begin try
-- your procedure starts
if condition
begin
statement
end
else
begin
RAISERROR(#, #, '')
end;
-- your procedure ends
end try
begin catch
print(ERROR_MESSAGE())
throw;
end catch;
Structure of RAISERROR from MS SQL Server docs:
RAISERROR ( { msg_id | msg_str | #local_variable }
{ ,severity ,state }
[ ,argument [ ,...n ] ] )
[ WITH option [ ,...n ] ]

How do I get an error message returned to my stored procedure?

I am calling StoredProcA from StoredProcB like this:
EXEC StoredProcA(#a,#b,#c)
StoredProcA has code similar to this:
do some stuff...
if #a < #b
insert into TableX(ItemNo, ItemDescr, ItemCost)
values(#q, #r, #c)
if ##ERROR <> 0 begin
rollback transaction
raiserror('Insert into TableX failed', 16, 1)
return
end
do more stuff...
So when I call StoredProcA from StoredProcB how do I get that error message returned to StoredProcB and assign a variable to it?
Also, I cannot change StoredProcA in any way.
I assume it would be something like this:
declare #err_msg varchar(50)
EXEC #err_msg = StoredProcA(#a,#b,#c)
update MyTable Set Result = #err_msg Where Cost = #a
but I just can't figure out the exact syntax...
You'll need to use T-SQL structured error handling to capture the error message text and reference with ERROR_MESSAGE() in the catch block:
BEGIN TRY
EXEC StoredProcA #a, #b, #c;
END TRY
BEGIN CATCH
UPDATE dbo.MyTable SET Result = ERROR_MESSAGE() WHERE Cost = #a;
END CATCH;

SQL Procedure behavior while having multiple if statements

I have stored procedure like this,
Create Procedure [dbo].[Get_Data](
#Id as Varchar(20),
#Type as Varchar(10)
)
As
Begin
IF(#Type = 'skill')
Begin
.....
select * ....
END
IF(#Type = 'agent')
Begin
.....
select * ....
END
IF(#Type = 'skillProfile')
Begin
Print 'abc'
select * ....
.....
END
END
Note: there is no syntax or any other error inside any of the if loops as every select query inside each loops are tested successfully.
So now, When i try to execute procedure using command as below,
EXEC [Get_Data] '1391520','skillProfile'
the statement print is not printed also dint get any rows in return, instead i get message Commands completed successfully.
I tried changing last(i.e. here third) if loop statement i.e. from IF(#Type = 'skillProfile') to IF(#Type = 'profile') and tried executing like EXEC [Get_Data] '1391520','profile' which worked all fine!
Got confused with this and tried changing first if loop and last(third) if loop like this, IF(#Type = 'skill') to IF(#Type = 'xyz') and IF(#Type = 'skillProfile') to IF(#Type = 'xyzProfile') and tried executing like EXEC [Get_Data] '1391520','xyzProfile' which also worked all fine.
Now i am totally confused why it doesn't work when i use skill and skillProfile.
You defined your parameter as Varchar(10), which means it cannot hold more than 10 characters. If you try to assign it a longer value, it gets truncated with no warning. So you are trying to execute your procedure with #Type='skillProfi'. The solution is to increase the size of your parameter, say varchar(100).
Use Try catch block you will let know where error is
Create Procedure [dbo].[Get_Data](
#Id as Varchar(20),
#Type as Varchar(10)
)
As
BEGIN TRY
SET NOCOUNT ON
IF(#Type = 'skill')
Begin
.....
select * ....
END
IF(#Type = 'agent')
Begin
.....
select * ....
END
IF(#Type = 'skillProfile')
Begin
Print 'abc'
select * ....
.....
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH

Return Resultset from SQL Server to VB.NET application

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().

Resources