Getting sp_tracegenerateevent to work in a stored procedure - sql-server

I'm having a hard-time debugging a stored procedure called from BizTalk.
In a prior thread, someone suggested using sp_trace_generateevent.
[Use SQL Debugger when stored proc called by an external process
Since I need to display a variable, I came up with the following, and it works, I can see the value in SQL Profiler (with EventClass=UserConfigurable:0"
Declare #message nvarchar(128)
Set #message = 'Hello World 2 '
exec sp_trace_generateevent #event_class=82, #userinfo = #message
But when I put it in a "BEGIN CATCH" in the problem stored proc, I don't see anything in the profiler:
BEGIN CATCH
DECLARE #message nvarchar(128)
SET #message = LTRIM(STR(ERROR_MESSAGE()))
exec sp_trace_generateevent #event_class=82, #userinfo = #message
SET #CatchErrors =
'Catch: [LTL].[CreateShipment] - ErrorNumber: '+LTRIM(STR(ERROR_NUMBER()))
+ ' ErrorSeverity: '+LTRIM(STR(ERROR_SEVERITY()))
+ ' ErrorState: '+LTRIM(STR(ERROR_STATE()))
+ ' ErrorProcedure: '+LTRIM(STR(ERROR_PROCEDURE()))
+ ' ErrorLine: '+LTRIM(STR(ERROR_LINE()))
+ ' ErrorMessage: '+LTRIM(STR(ERROR_MESSAGE()))
END CATCH
So then I put a Catch within the Catch:
BEGIN CATCH
BEGIN TRY
DECLARE #message nvarchar(128)
SET #message = LTRIM(STR(ERROR_MESSAGE()))
exec sp_trace_generateevent #event_class=82, #userinfo = #message
END TRY
BEGIN CATCH
SET #Message = 'Error in sp_trace_generateevent'
END CATCH
SET #CatchErrors =
'Catch: [LTL].[CreateShipment] - ErrorNumber: '+LTRIM(STR(ERROR_NUMBER()))
+ ' ErrorSeverity: '+LTRIM(STR(ERROR_SEVERITY()))
+ ' ErrorState: '+LTRIM(STR(ERROR_STATE()))
+ ' ErrorProcedure: '+LTRIM(STR(ERROR_PROCEDURE()))
+ ' ErrorLine: '+LTRIM(STR(ERROR_LINE()))
+ ' ErrorMessage: '+LTRIM(STR(ERROR_MESSAGE()))
END CATCH
And now I can see "SET #Message = 'Error in sp_trace_generateevent' " in the profiler, but I really need to see the reason for the error.
The problem I've having cannot be reproduced when testing in SSMS, only when I call from BizTalk. My intent is to bubble the #CatchErrors (as an output parameter) back to BizTalk, but it's not working either.
Also - BizTalk is running with a user that has SQL SysAdmin (it's on my development machine).
Also same result when using master..sp_tracegeneratedevent
Based on #Jeroen's reply, I switched to this, but still getting some error caught.
DECLARE #message nvarchar(128)
BEGIN TRY
SET #message = Convert(nvarchar(128),SUBSTRING(ERROR_MESSAGE(),1,128))
exec sp_trace_generateevent #event_class=82, #userinfo=#message
END TRY
Update #1: This is driving me batty. When I test in SQL it works, but when I test from BizTalk it doesn't. So I really want a debug feature. I now have catch on my catches on my catches... and they are all catching and I don't know why. Same code works fine in the divide by zero simple example. To further complicate, this is a stored proc, called by a stored proc, called by BizTalk. If I catch the error, I should be able to return it to BizTalk in the output parameter called #CatchErrors in my main and sub-stored proc.
BEGIN CATCH
DECLARE #message nvarchar(128)
BEGIN TRY
SET #message = Convert(nvarchar(128),SUBSTRING(ERROR_MESSAGE(),1,128))
exec sp_trace_generateevent #event_class=82, #userinfo=#message
END TRY
BEGIN CATCH
SET #Message = 'Error in sp_trace_generateevent'
END CATCH
BEGIN TRY
SET #CatchErrors =
'Catch: [RG].[CreateShipment] - ErrorNumber: '+CAST(ERROR_NUMBER() AS VARCHAR(35))
+ ' ErrorSeverity: '+CAST(ERROR_SEVERITY() AS VARCHAR(35))
+ ' ErrorState: '+CAST(ERROR_STATE() AS VARCHAR(35))
+ ' ErrorProcedure: '+CAST(IsNull(ERROR_PROCEDURE(),'') AS VARCHAR(200))
+ ' ErrorLine: '+CAST(ERROR_LINE() AS VARCHAR(35))
+ ' ErrorMessage: '+CAST(ERROR_MESSAGE() AS VARCHAR(4000))
END TRY
BEGIN CATCH
BEGIN TRY
SET #Message = 'Error in Set #CatchErrors='
SET #CatchErrors =
'Catch: [LTL.CreateShipmentStopLineItem]- Error: ' + CAST(ERROR_MESSAGE() AS VARCHAR(4000))
END TRY
BEGIN CATCH
SET #Message = 'Error in Set #CatchErrors2'
END CATCH
END CATCH
END CATCH
Current Profiler Result:
Update #2 - Testing in SSMS:
I'm testing in SSMS, and none of the catches have issues. If i run this more than once it gets Violation of Primary Key in the Print statement.
Declare #shipstopline LTL.TT_ShipmentStopLineItem
DECLARE #messageID bigint
DECLARE #CatchErrorsResult varchar(max)
insert into #shipstopline values ('2', '1', 'Eggs','1','2','3','1','100','1','12','1','1','1','10','20','1')
EXEC LTL.CreateShipmentStopLineItem #MessageId = 2, #ShipmentStopID=1, #CreateBy=108004, #ShipmentStopLineItem=#shipstopline, #loopid=1, #catchErrors=#CatchErrorsResult OUT
select RowCreated, * from LTL.ShipmentStopLineItem order by LTL.ShipmentStopLineItem.RowCreated desc
print #CatchErrorsResult

Well, if you try this:
SET XACT_ABORT ON;
BEGIN TRY
SELECT 1 / 0;
END TRY
BEGIN CATCH
DECLARE #message nvarchar(128);
SET #message = LTRIM(STR(ERROR_MESSAGE()));
exec sp_trace_generateevent #event_class=82, #userinfo = #message
END CATCH
You'll get immediate feedback on what's wrong:
Msg 8114, Level 16, State 5, Line 8 Error converting data type
nvarchar to float.
And that's because that STR() call you've got is not the right thing to use -- STR formats floating-point values, and only those. (For flexible conversion, use FORMAT and/or CONCAT, the latter of which always implicitly converts things to strings without complaining.)
Aside from that, the first parameter of the stored procedure is #eventid, not #event_class (this is normally an error, but extended stored procedures are more flexible that way -- you could call the parameter #banana and it will still work). Still, for clarity's sake we should use the documented name. So:
SET XACT_ABORT ON;
BEGIN TRY
SELECT 1 / 0;
END TRY
BEGIN CATCH
DECLARE #message nvarchar(128) = ERROR_MESSAGE();
EXEC sp_trace_generateevent #eventid=82, #userinfo = #message;
END CATCH
And in my profiler, this produces a UserConfigurable:0 event with
Divide by zero error encountered.
Note that if there is no TRY / CATCH, you should still be able to get errors even without generating your own trace event, through the Exception event.

It turns out that BizTalk calls the Stored Proc twice, once with FmtOnly=On and once again with FmtOnly=Off. I was getting the invalid cast exception after the first call, so the Stored Proc was essentially not really executing. I was mislead by the profiler because of the FmtOnly=On, thinking that it was actually executing the statements I saw there. Thus the sp_TraceGenerateEvent doesn't actually run on the first pass with FmtOnly=On, but later when I got past the other errors, it works fine in the second pass with FmtOnly=Off.
Reference: Similar questoin I posted about why the SQL Profiler was looking different between an SSMS run and a BizTalk run of same stored proc: Can SQL Begin Try/Catch be lying to me (in the profiler)?

Related

Dynamic SQL executed inside a stored procedure in a TRY/CATCH block but the error is not returned

I have a simple stored procedure shown below. As you can see, the error is forcefully generated inside the dynamic SQL and passed to the CATCH block. The problem is that the details I expect are not returned. All I get back is this message:
Msg 50000, Level 16, State 1, Procedure myproc, Line 35 [Batch Start Line 2]
Why don't I get back the information as coded in the CATCH block - procedure name, error line and error message?
Here is my procedure:
CREATE PROCEDURE myproc
AS
SET XACT_ABORT ON
BEGIN TRY
DECLARE #nSQL NVARCHAR(MAX) = 'SELECT 1/0'
EXEC sp_executesql #nSQL
END TRY
BEGIN CATCH
DECLARE #ERROR_NUMBER INT;
DECLARE #ERROR_SEVERITY INT;
DECLARE #ERROR_STATE INT;
DECLARE #ERROR_PROCEDURE NVARCHAR(128);
DECLARE #ERROR_LINE INT;
DECLARE #ERROR_MESSAGE NVARCHAR(4000);
SET #ERROR_NUMBER = ERROR_NUMBER();
SET #ERROR_SEVERITY = ERROR_SEVERITY();
SELECT #ERROR_STATE = CASE
WHEN ERROR_STATE() = 0 THEN 1
ELSE ERROR_STATE()
END;
SET #ERROR_PROCEDURE = ERROR_PROCEDURE();
SET #ERROR_LINE = ERROR_LINE();
SET #ERROR_MESSAGE = 'ERROR in ' + #ERROR_PROCEDURE + '; (ERROR Line: ' + CAST(#ERROR_LINE AS NVARCHAR(5)) + '); ' + 'ERROR MESSAGE: '
+ ERROR_MESSAGE() + ';';
RAISERROR(#ERROR_MESSAGE, #ERROR_SEVERITY, #ERROR_STATE);
RETURN (1);
END CATCH;
"ERROR_PROCEDURE (Transact-SQL)":
ERROR_PROCEDURE returns NULL if the error did not occur within a stored procedure or trigger.
Since the dynamic SQL is not a procedure nor a trigger, this applies here and your #ERROR_PROCEDURE is NULL.
Now you use #ERROR_PROCEDURE in a string concatenation using + and assign that to #ERROR_MESSAGE. If you lookup the remarks in "+ (String Concatenation) (Transact-SQL)", you'll see that this can lead to the result of the concatenation being NULL and that likely happens in your case.
When you work with strings with a null value, the result of the concatenation depends on the session settings. Just like arithmetic operations that are performed on null values, when a null value is added to a known value the result is typically an unknown value, a string concatenation operation that is performed with a null value should also produce a null result. However, you can change this behavior by changing the setting of CONCAT_NULL_YIELDS_NULL for the current session.
So you pass a NULL as the first argument for RAISERROR and therefore no message is printed.
You can use concat() instead of + for the concatenation or replace NULLs manually.
But you should avoid RAISERROR anyway. See the note in "RAISERROR (Transact-SQL)".
The RAISERROR statement does not honor SET XACT_ABORT. New applications should use THROW instead of RAISERROR.
ERROR_PROCEDURE() is NULL because the error occurred in the inner scope of the dynamic SQL statement, not in the stored procedure itself. If you want the name of the current proc, use OBJECT_NAME(##PROCID).
If you are using SQL Server 2012 or above version, it is recommended to use THROW.
Changing your whole CATCH block to below should be enough:
BEGIN CATCH
THROW
END CATCH;
Msg 8134, Level 16, State 1, Line 1
Divide by zero error encountered.

How to get error description once error occured during sql statement execution

I am using SQL Server 2008 and writing a stored procedure which inserts and updates records in a table. This procedure will be scheduled for some time. Whenever there is a error I am logging that error in another table. But I need actual error message but unable to get the same.
I have used this:
SET #ErrNum = ##ERROR
SET #ErrMsg = ERROR_MESSAGE()
But my problem is I'm not getting the actual error message. I am able to get error number.
Please suggest what needs to be done for actual message to get from SQL Server prompt after execution of SQL statement.
ERROR_MESSAGE() should do the trick.
BEGIN TRY
SELECT 1/0
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH;
This is valid what you're doing. In all scripts you can see something like that
DECLARE #ErrorMessage nvarchar(max)
DECLARE #ErrorMessageOriginal nvarchar(max)
DECLARE #Error int
DECLARE #ReturnCode int
SET #Error = 0
SET #ReturnCode = 0
SET #Error = ERROR_NUMBER()
SET #ReturnCode = #Error
SET #ErrorMessageOriginal = ERROR_MESSAGE()
SET #ErrorMessage = 'Msg ' + CAST(#Error AS nvarchar) + ', ' + ISNULL(#ErrorMessageOriginal,'')
RAISERROR(#ErrorMessage,16,1) WITH NOWAIT
But there is error that you want to handle by yourself like that from MSDN source:
Using ##ERROR to detect a specific error
USE AdventureWorks2012;
GO
UPDATE HumanResources.EmployeePayHistory
SET PayFrequency = 4
WHERE BusinessEntityID = 1;
IF ##ERROR = 547
PRINT N'A check constraint violation occurred.';
GO

tSQLt There was also a ROLLBACK ERROR in Private_RunTest

Whenever I use ExpectException I receive the following error: (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,140})
It appears that tSQLt is attempting a ROLLBACK after the fact that MSSQL has already performed the ROLLBACK due to the RAISERROR.
I wrapped the following SELECT and SET statement in Private_RunTest with the following IF statement and it appeared to resolve the problem.
IF ISNULL(#ExpectException,0) <> 1
BEGIN
SELECT #Msg = COALESCE(#Msg, '') + ' (There was also a ROLLBACK ERROR --> ' +
COALESCE(ERROR_MESSAGE(), '') + '{' +
COALESCE(ERROR_PROCEDURE(), '') + ',' +
COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '') + '})';
SET #Result = 'Error';
END
Is this truly a bug and/or an appropriate fix?
You are probably not using TRY/CATCH blocks in your tsqlt test.
The error message "There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction." can be reproduced as follows:
1) Create a Trigger that throws an error and rolls back as follows:
CREATE TRIGGER [dbo].[MyTable_IUDTR] ON [dbo].[MyTable]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
BEGIN TRY
RAISERROR('MyError', 16, 1)
END TRY
BEGIN CATCH
THROW;
END CATCH
END
GO
2) Create a tsqlt test to check the error message returned:
CREATE PROC [ut_MyTable_IUDTR].[test that the error is returned]
AS
BEGIN
DECLARE #ErrorMsg VARCHAR(50)
EXEC tsqlt.FakeTable #TableName = 'MyTable'
EXEC tsqlt.ApplyTrigger 'MyTable', 'MyTable_IUDTR'
INSERT INTO dbo.MyTable
( FirstName, LastName )
VALUES ( N'John',N'Smith')
SET #ErrorMsg = ERROR_MESSAGE()
EXEC tSQLt.AssertEqualsString #Expected = 'MyError', #Actual = #ErrorMsg
END
GO
3) Run the test:
EXEC [tSQLt].Run 'ut_MyTable_IUDTR.test that the error is returned'
4) You get the following Error:
There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
THE FIX:
Change the tsqlt test to include TRY/CATCH blocks as follows:
ALTER PROC [ut_MyTable_IUDTR].[test that the error is returned]
AS
BEGIN
DECLARE #ErrorMsg VARCHAR(50)
EXEC tsqlt.FakeTable #TableName = 'MyTable'
EXEC tsqlt.ApplyTrigger 'MyTable', 'MyTable_IUDTR'
BEGIN TRY
INSERT INTO dbo.MyTable
( FirstName, LastName )
VALUES ( N'John',N'Smith')
END TRY
BEGIN CATCH
SET #ErrorMsg = ERROR_MESSAGE()
END CATCH
EXEC tSQLt.AssertEqualsString #Expected = 'MyError', #Actual = #ErrorMsg
END
GO
You may be using SET XACT_ABORT ON in your tsqlt test
CREATE PROC [testClass].[test foo]
AS
BEGIN
SET XACT_ABORT ON
EXEC tsqlt.AssertEquals #Expected = 1, #Actual = 0
END
Don't do this.
You'll get a message like this
[testClass].[test foo] failed: (Error) Expected: <1> but was: <0> (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,160})

SSIS Package and Raising Error

I apologize is this is a repeat. I couldn't find a question that was answered that fit what I was asking. (this is similar How can i create out parameters with RAISERROR in stored procedure?)
I have a very simple job in SQL Server Agent with one step. The package only executes a stored procedure. The stored procedure has a try / catch block and when I try to cause an exception it is caught but doesn't propagate up and cause the job to report an error. The job succeeds. I log the error into a local table and commit it so I know I am indeed entering the catch block. I have tried raising error with severity levels 16 and 20 and some other things that were probably not worth mentioning.
Is this a package setting that would cause it to not propagate or is a problem with my stored procedure? Thanks.
-- dumbed down structure
BEGIN
SET NOCOUNT ON;
-- buncha vars
BEGIN TRY
BEGIN TRANSACTION
set #StepNumber = 10;
select 1/0;
COMMIT TRANSACTION;
RETURN 0;
END TRY
BEGIN CATCH
-- get the error
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
-- rollback the transaction
ROLLBACK TRANSACTION;
-- log the caught exception into the local log
set #LogTimestamp = GETUTCDATE();
set #LogMessage = #SessionGUID + ' - ' + CAST(#StepNumber AS NVARCHAR) + ' - ' + ' ERROR - ENTERED CATCH BLOCK - ERROR_MESSAGE()=' + #ErrorMessage + ' ERROR_SEVERITY()=' + cast(#ErrorSeverity as nvarchar) + ' ERROR_STATE()=' + CAST(#ErrorState as nvarchar) ;
insert into #LocalLog(TimestampDT, SourceVch, MessageVch) values (#LogTimestamp, #LogSource, #LogMessage);
-- transfer local log to master log table
BEGIN TRANSACTION;
--insert into RealTable selecting from #LocalLog;
COMMIT TRANSACTION;
-- slightly modify the error message before raising it to parent process
SET #ErrorMessage = #LogSource + ' - error with #StepNumber=[' +CAST(#StepNumber as nvarchar)+ '] ERROR_MESSAGE()=' + #ErrorMessage;
-- rethrow the error to the calling process
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState);
-- do we need this return?
RETURN #StepNumber;
END CATCH
END
The log table I get tells me I caught the error at least (From my log table).
"6a3e80fd-f480-459c-a42f-25fd3d5a42a8 - 125 - ERROR - ENTERED CATCH BLOCK - ERROR_MESSAGE()=Divide by zero error encountered. ERROR_SEVERITY()=16 ERROR_STATE()=1"

How does one catch the error message "Error Converting data type varchar to decimal"

I have a stored procedure that performs an update with a try catch block. The column (discountPercentage) is set as a decimal data type. I want the stored procedure to catch if the user inserts the discountPercentage as anything other than a decimal, for example if they try to insert 45%. I'm using the ERROR_MESSAGE function and for some reason it's not catching the error message. I still receive the error message that states "Error converting data type varchar to decimal." Here is my code so far:
alter procedure procUpdateFoodTitleConditionDiscount
(
#foodTitleId int,
#conditionId int,
#discountPercentage decimal(4,2),
#errorMessage varchar(100) output
)
as
begin
set #errorMessage = 'Discount updated'
begin try
update Discount
set discountPercentage = #discountPercentage
where foodTitleId = #foodTitleId and conditionId = #conditionId
if (##ROWCOUNT = 0)
begin
set #errorMessage = 'Update not successful.'
end
end try
begin catch
if (ERROR_MESSAGE () like '%converting%')
begin
set #errorMessage = 'Please Insert Discount Percentage as a Decimal (ex 0.45)'
end
end catch
end;
declare #errorMessageValue varchar (100)
execute procUpdateFoodTitleConditionDiscount
#foodTitleId = '104',
#conditionId = '4',
#discountPercentage = '45%',
#errorMessage = #errorMessageValue output
print #errorMessageValue
Assuming your attempting something like this
Exec procUpdateFoodTitleConditionDiscount 1,1,'45%'
The signature of your procedure is preventing any of the proc from being executed.
For example if you added
begin
PRINT 'foo'
set #errorMessage = 'Discount updated'
'foo' would never be printed.
If you want the behavior you've described you'll need to change #discountPercentage decimal(4,2), to #discountPercentage varchar(10)
You can't trap this error in the proc: it happens before your code runs.
This needs cleaned in the client code. After all 45% is 0.45.
SQL Server simply isn't good at this
You could change the parameter to varchar and do some manipulation but that's just plain silly

Resources