raiserror shows one message, but not another - sql

I have this code:
DECLARE
#timestamp varchar(20),
#latestFeed INT,
#LargestKeyProcessed INT,
#NextBatchMax INT,
#RC INT;
SET #timestamp = CAST(CURRENT_TIMESTAMP as varchar(20));
SET #latestFeed = (SELECT MAX([feed_id]) FROM [dbo].[CAQH_RESP_ALL_TEST_MIRROR]);
SET #LargestKeyProcessed = (SELECT MIN([record_id]) - 1 FROM [dbo].[CAQH_RESP_ALL_TEST_MIRROR] WHERE [feed_id] = #latestFeed);
SET #NextBatchMax = 1;
SET #RC = (SELECT MAX([record_id]) FROM [dbo].[CAQH_RESP_ALL_TEST_MIRROR]);
raiserror(#timestamp, 0, 1) with nowait
raiserror(#LargestKeyProcessed, 0, 2) with nowait
WHILE (#NextBatchMax < #RC)
BEGIN
BEGIN TRY
--do some stuff
COMMIT TRANSACTION flagHandling
raiserror('Transaction Committed', 0, 3) with nowait
raiserror(#timestamp, 0, 4) with nowait
raiserror(#LargestKeyProcessed, 0, 5) with nowait
END TRY
BEGIN CATCH
--catch some stuff
END CATCH
END
It seems to run fine, but has a couple of things that seem odd to me. At the outset, it prints the date as I'd expect to see it, but then prints an actual error message with the raiserror I'm using. I'm using the exact same syntax, but one prints only the desired timestamp, while the next produces
Jul 23 2015 9:09AM
Msg 18054, Level 16, State 1, Line 16
Error 33218606, severity 0, state 2 was raised, but no message with that
error number was found in sys.messages. If error is larger than 50000,
make sure the user-defined message is added using sp_addmessage.
Then, after the transaction is committed, it correctly shows the following messages:
Transaction Committed
Jul 23 2015 9:11AM
But leaves out the last message which should show the value of #LargestKeyProcessed. This raiserror message is the same one as the raiserror message at the beginning of the script that produces the aforementioned odd behavior.
I just want to have the messages print to the messages window without looking like an error, and also want to have ALL the messages print. What am I doing wrong here?

You're giving an int to raiserror, which means you're using the msg_id functionality:
RAISERROR ( { msg_id | msg_str | #local_variable }
{ ,severity ,state }
[ ,argument [ ,...n ] ] )
[ WITH option [ ,...n ] ]
msg_id:
Is a user-defined error message number stored in the sys.messages
catalog view using sp_addmessage. Error numbers for user-defined error
messages should be greater than 50000. When msg_id is not specified,
RAISERROR raises an error message with an error number of 50000.
You should use varchar variables, and that can then contain the number which you want to send. See the documentation.

Related

Passing Cursor Result to send_dbmail as a Parameter

I've never worked with cursors before, and upon reading, this may not be the best approach, so by all means, makes suggestions.
I am attempting to pass the result set of a cursor to a query. Here's what I have so far:
DECLARE #PM varchar(50),
#c1 as CURSOR
SET #c1 = CURSOR FOR
SELECT PM
FROM PMtable
OPEN #c1;
FETCH NEXT FROM #c1 INTO #PM;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #emailBody nvarchar(max)
SET #emailBody = 'SELECT * FROM othertable WHERE PM = ' + #PM + ' ORDER BY PM';
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'me#me.com',
#subject = 'test',
#query = #emailBody;
FETCH NEXT FROM #c1 INTO #PM;
END
CLOSE #c1;
DEALLOCATE #c1;
The idea is to send the #emailBody query result set as an email for every result in the cursor. For example, say the cursor returns three results: Bob, Jim, and Joe. I want to loop run the #emailBody query for each result from the cursor and send an email for each result.
When I run the query as is, I receive an error saying:
Msg 22050, Level 16, State 1, Line 0 Error formatting query, probably
invalid parameters
Msg 14661, Level 16, State 1, Procedure
sp_send_dbmail, Line 504 [Batch Start Line 0]
Query execution failed:
Msg 207, Level 16, State 1, Server SERVER, Line 9 Invalid column name
'Bob'.
Msg 207, Level 16, State 1, Server SERVER, Line 1 Invalid
column name 'Bob'.
I have no clue what's going on. Any ideas?
You need to add '':
SET #emailBody='SELECT * FROM othertable WHERE PM = ''' + #PM + ''' ORDER BY PM';
Be aware of possible SQL Injection.
How it works:
Msg 207, Level 16, State 1, Server SERVER, Line 9 Invalid column name 'Bob'.
SELECT * FROM othertable WHERE PM = Bob ORDER BY PM
vs.
SELECT * FROM othertable WHERE PM = 'Bob' ORDER BY PM
Please keep in mind that ORDER BY PM for one value does not change anything.

customize an error message in SQL server 2008 R2 on win 7

I need to customize an error message in SQL server 2008 R2 (no support for throw) on win 7.
BEGIN TRY
DECLARE #result INT
SET #result = LOG(-1)
END TRY
BEGIN CATCH
EXEC sys.sp_addmessage 60000, 16, 'log() argument is not positive'
RAISERROR (60000, 16, 1)
END CATCH
But, the result is always the same :
An invalid floating point operation occurred.
Thanks
You can get around this by using a variable for the Log argument:
DECLARE #result INT
DECLARE #insidelog INT = -1
IF #insidelog < 0
BEGIN
EXEC sys.sp_addmessage 60000, 16, 'log() argument is not positive'
RAISERROR (60000, 16, 1)
END
ELSE
BEGIN
SET #result = LOG(#insidelog)
END
I assume you'd be passing that number as a variable anyway.

Throw exception from SQL Server function to stored procedure

I have stored procedure in SQL Server 2012 say spXample and a scaler-valued function say fXample.
I call a function fXample from spXample.
Can I throw an exception in function and catch it in stored procedure's Catch block and rethrow to the calling C# code?
Update:
The function I wrote like:
CREATE FUNCTION dbo.fXample(#i INT)
RETURNS TINYINT
AS
BEGIN
RETURN (SELECT CASE WHEN #i < 10
THEN THROW 51000,'Xample Exception',1;
ELSE (SELECT #i)
END);
END
GO
I am getting error
Msg 443, Level 16, State 14, Procedure fXample, Line 46 Invalid use of
a side-effecting operator 'THROW' within a function.
How do I write alternative code to achieve above functionality?
You can do this by forcing an error condition when your validation fails, provided that isn't a possible error that might occur naturally. When you know a certain error can only occur when validation has failed, you can handle that in a custom way by checking for that error_number in your catch block. Example in tempdb:
USE tempdb;
GO
CREATE FUNCTION dbo.fXample(#i INT)
RETURNS TINYINT
AS
BEGIN
RETURN (SELECT CASE WHEN #i < 10 -- change this to your "validation failed" condition
THEN 1/0 -- something that will generate an error
ELSE (SELECT #i) -- (you'd have your actual retrieval code here)
END);
END
GO
CREATE PROCEDURE dbo.spXample
#i INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
SELECT dbo.fXample(#i);
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8134 -- divide by zero
BEGIN
THROW 50001, 'Your custom error message.', 1;
-- you can throw any number > 50000 here
END
ELSE -- something else went wrong
BEGIN
THROW; -- throw original error
END
END CATCH
END
GO
Now try it out:
EXEC dbo.spXample #i = 10; -- works fine
EXEC dbo.spXample #i = 6; -- fails validation
EXEC dbo.spXample #i = 256; -- passes validation but overflows return
Results:
----
10
Msg 50001, Level 16, State 1, Procedure spXample, Line 12
Your custom error message.
Msg 220, Level 16, State 2, Procedure spXample, Line 7
Arithmetic overflow error for data type tinyint, value = 256.
/* *** EXAMPLES ***
SELECT dbo.fnDoSomething(500) -- RETURNS TRUE
SELECT dbo.fnDoSomething(5000) -- THROWS ERROR
Msg 245, Level 16, State 1, Line 4
Conversion failed when converting the varchar value
'Function Error - [dbo].[fnDoSomething] - Value is greater than 1000' to data type bit.
*/
CREATE FUNCTION dbo.fnDoSomething
(
#SomeValue INT
)
RETURNS BIT
AS
BEGIN
IF #SomeValue > 1000
BEGIN
DECLARE #FunctionName AS SYSNAME = QUOTENAME(OBJECT_SCHEMA_NAME(##PROCID)) + '.' + QUOTENAME(OBJECT_NAME(##PROCID))
DECLARE #Error AS VARCHAR(200) = 'Function Error - '+ #FunctionName + ' - Value is greater than 1000'
RETURN CONVERT(BIT,#Error)
END
RETURN 1
END
GO
Although this has been solved but I feel I should still share this. As I think it will be a simpler solution:
BEGIN TRY
THROW 60000, 'Error', 1;
END TRY
BEGIN CATCH
THROW
END CATCH
You can also raise an error in the stored procedure if a certain condition is met but the error severity has to be higher than 10 to throw an SQL exception
CREATE PROC test
AS
RAISERROR('the error message', 11, 1);
RETURN
you can access this message later in your catch block as such
try {
testProc.ExecuteNonQuery();
}
catch(SqlException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
Output : "the error message"

Procedure stops when legacy, new error traps are next to each other

We have a bunch of old stored procedures with legacy style error trapping. I changed one the other day and included a newer TRY...CATCH block. The stored procedure just stopped after the TRY/CATCH and returned as though there were an error in the legacy block.
If I put a
SELECT NULL
in between the two everything works fine. Anyone know why this is happening?
--BEGIN NEW ERROR TRAP--
BEGIN TRY
Do stuff...
END TRY
BEGIN CATCH
END CATCH
--END NEW ERROR TRAP---
----------------- OLD SCHOOL TRAP BEGIN -----------------
SELECT #spERROR = ##ERROR ,
#spROWCOUNT = ##ROWCOUNT
SET #spRETURN = #spRETURN + 1
IF ( #spROWCOUNT <= 0
OR #spERROR <> 0
)
SET #spRETURN = 0 - #spRETURN
IF ( #spROWCOUNT <= 0
OR #spERROR <> 0
)
RETURN #spRETURN
SELECT #spROWCOUNT = -1 ,
#spERROR = -1
------------------ OLD SCHOOL ERROR TRAP END ------------------
In your try catch block, the last statement is probably doing something that sets the row count to 0. The "SELECT NULL" is setting the row count to 1, since it returns one row, so no error is detected.
You can fix this by changing the logic in the "old" code or by setting your row count variable in the try/catch code. I would recommend that you remove the SELECT NULL, since it would guarantee success and you may not want that behavior.

The "right" way to do stored procedure parameter validation

I have a stored procedure that does some parameter validation and should fail and stop execution if the parameter is not valid.
My first approach for error checking looked like this:
create proc spBaz
(
#fooInt int = 0,
#fooString varchar(10) = null,
#barInt int = 0,
#barString varchar(10) = null
)
as
begin
if (#fooInt = 0 and (#fooString is null or #fooString = ''))
raiserror('invalid parameter: foo', 18, 0)
if (#barInt = 0 and (#barString is null or #barString = ''))
raiserror('invalid parameter: bar', 18, 0)
print 'validation succeeded'
-- do some work
end
This didn't do the trick since severity 18 doesn't stop the execution and 'validation succeeded' is printed together with the error messages.
I know I could simply add a return after every raiserror but this looks kind of ugly to me:
if (#fooInt = 0 and (#fooString is null or #fooString = ''))
begin
raiserror('invalid parameter: foo', 18, 0)
return
end
...
print 'validation succeeded'
-- do some work
Since errors with severity 11 and higher are caught within a try/catch block another approach I tested was to encapsulate my error checking inside such a try/catch block. The problem was that the error was swallowed and not sent to the client at all. So I did some research and found a way to rethrow the error:
begin try
if (#fooInt = 0 and (#fooString is null or #fooString = ''))
raiserror('invalid parameter: foo', 18, 0)
...
end try
begin catch
exec usp_RethrowError
return
end catch
print 'validation succeeded'
-- do some work
I'm still not happy with this approach so I'm asking you:
How does your parameter validation look like? Is there some kind of "best practice" to do this kind of checking?
I don't think that there is a single "right" way to do this.
My own preference would be similar to your second example, but with a separate validation step for each parameter and more explicit error messages.
As you say, it's a bit cumbersome and ugly, but the intent of the code is obvious to anyone reading it, and it gets the job done.
IF (ISNULL(#fooInt, 0) = 0)
BEGIN
RAISERROR('Invalid parameter: #fooInt cannot be NULL or zero', 18, 0)
RETURN
END
IF (ISNULL(#fooString, '') = '')
BEGIN
RAISERROR('Invalid parameter: #fooString cannot be NULL or empty', 18, 0)
RETURN
END
We normally avoid raiseerror() and return a value that indicates an error, for example a negative number:
if <errorcondition>
return -1
Or pass the result in two out parameters:
create procedure dbo.TestProc
....
#result int output,
#errormessage varchar(256) output
as
set #result = -99
set #errormessage = null
....
if <errorcondition>
begin
set #result = -1
set #errormessage = 'Condition failed'
return #result
end
I prefer to return out as soon an possible, and see not point to having everything return out from the same point at the end of the procedure. I picked up this habit doing assembly, years ago. Also, I always return a value:
RETURN 10
The application will display a fatal error on positive numbers, and will display the user warning message on negative values.
We always pass back an OUTPUT parameter with the text of the error message.
example:
IF ~error~
BEGIN
--if it is possible to be within a transaction, so any error logging is not ROLLBACK later
IF XACT_STATE()!=0
BEGIN
ROLLBACK
END
SET #OutputErrMsg='your message here!!'
INSERT INTO ErrorLog (....) VALUES (.... #OutputErrMsg)
RETURN 10
END
I always use parameter #Is_Success bit as OUTPUT. So if I have an error then #Is_success=0. When parent procedure checks that #Is_Success=0 then it rolls back its transaction(with child transactions) and sends error message from #Error_Message to client.
As you can see from this answer history I followed this question and accepted answer, and then proceeded to 'invent' a solution that was basically the same as your second approach.
Caffeine is my main source of energy, due to the fact that I spend most of my life half-asleep as I spend far too much time coding; thus I didn't realise my faux-pas until you rightly pointed it out.
Therefore, for the record, I prefer your second approach: using an SP to raise the current error, and then using a TRY/CATCH around your parameter validation.
It reduces the need for all the IF/BEGIN/END blocks and therefore reduces the line count as well as puts the focus back on the validation. When reading through the code for the SP it's important to be able to see the tests being performed on the parameters; all the extra syntactic fluff to satisfy the SQL parser just gets in the way, in my opinion.