SQL Server: how to test a trigger that doesn't throw errors but only prints statements? - sql

My trigger checks if a condition is true, and if so throws a warning with a PRINT statement. This later gets caught by the JDBC, for use in the front-end.
My question: how can I make a test for the trigger in T-SQL? I have a testing USP that checks if ERROR_NUMBER equals the specified trigger error, but that only works if the trigger actually throws said error message, which this trigger isn't supposed to do.
Is there an alternative to using a PRINT perhaps, that I can catch in my testing USP, without interrupting the statement? (it is supposed to function anyway)
To make it more concrete: my database is about fishes and aquaria. When you put a carnivorous fish and a herbivorous fish in the same tank, it should give a warning (which my front-end will catch), but not restrict the action. How do I check if that warning was given using a testing USP?

I believe you are looking for RAISEERROR.
From the article:
RAISERROR can be used as an alternative to PRINT to return messages to calling applications. RAISERROR supports character substitution similar to the functionality of the printf function in the C standard library, while the Transact-SQL PRINT statement does not. The PRINT statement is not affected by TRY blocks, while a RAISERROR run with a severity of 11 to 19 in a TRY block transfers control to the associated CATCH block. Specify a severity of 10 or lower to use RAISERROR to return a message from a TRY block without invoking the CATCH block.
So you can use raiserror with a severity of 10 or lower if you don't want to go into the catch block or 11 to 19 if you want to take action (such as removing the inserted data) in the catch block.
Edit:
Since you can't use both msg_id and msg_str you would need to use sp_addmessage to add the error to the sys.messages catalog first then use msg_id instead of msg_string.
From the linked article:
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.

Related

Is there a way to process a catch block without invalidating the current transaction?

I have some TSQL that takes in WKT representations of geography and processes them.
Part of the processing is to check that they are valid bits of WKT before using them.
To do this I use the STIsValid() fucntion. When this function sees WKT with coordinates it considers are invalid it returns 0 (you can then make them valid with .MakeValid().
But, if it sees something that is clearly not WKT it throws an error, e.g.
geography::STGeomFromText('I_AM_NOT_WKT', 4326).STIsValid() = 0
Gives:
System.FormatException: 24114: The label I_AM_NOT_WKT in the input well-known text (WKT) is not valid.
Rather than try to write something that pre validates WKT to get it to a level that SQL can check, I decided to use a TRY CATCH block to get the records with bad WKT without crashing the routine. This works fine. The problem is if I do that it invalidates the transaction I've got going.
This is the basic structure of my TSQL:
BEGIN TRY
BEGIN TRANSACTION
…
--Doing some stuff
…
TRY
Is the current string valid WKT format (test using STIsValid())
NO?
Make the string valid using .MakeValid()
END TRY
CATCH
--The string was not valid WKT format
-- It's bad so write it to the bad table. !!! This causes the error !!!
END CATCH
…
--doing more stuff
…
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
END CATCH
As soon as I get a bad WKT string to test the whole transaction is doomed and therefore as soon as I try to write to the bad table I get the error:
" The current transaction cannot be committed and cannot support operations that write to the log file"
Which is to be expected.
My question is, is there a way to prevent the CATCH block invalidating my transaction? Or can anyone see another way around this?
Thanks

SqlException Try-Catch with vb.net

I'm trying to wrap my head around Try-Catch statements in vb.net. I am trying to handle a SQL exception error that occurs, but I'm not sure what to do with it? The particular method that contains the code sample below is also expecting a return value.
Try
Dim records As DataRecordCollection = Sql Insert SP call
Catch sqlEx As SqlException
Select Case sqlEx.Number
Case 547
*****What goes here?*****
Case Else
Throw
End Select
End Try
You can put whatever you want there. We can't give you the answer to this question, because it could be anything. It could also be nothing; you don't have to put anything at all in that place, though it's usually poor practice to just swallow an exception like that.
The point is what you put there depends entirely on your application. You might make a log entry, or clean up the error to show something nicer to the user, or put other code there to recover or try again, or even all of the above. Whatever you want. But we can't know what you want to do. That's up to you and your design specification.
Also, you can simplify this code using a conditional exception:
Try
Dim records As DataRecordCollection = Sql Insert SP call
Catch sqlEx As SqlException When sqlEx.Number = 547
// Put whatever you want here
End Try
Finally, in my experience the best option is usually to skip the Try/Catch block at this level entirely.
If you have a well-designed application, your database access is abstracted away into it's own class, assembly, namespace, or some combination thereof, that is separate from the UI or Business layer. My experience is that handling these exceptions in the database code is not as helpful as allowing the exception to bubble up to a higher level of abstraction. You'll be better positioned to deal with it there. That's kind of what Try/Catch is all about... that exceptions can be caught at the level that is most appropriate to that kind of exception.
This is especially true when you don't even know what you want to do. If you don't have a plan to handle an exception, then don't handle it. Ditch the Try/Catch block and let the exception bubble up to a higher level where maybe someone else has a better strategy for it.
The Microsoft SQL server stores the error messages in the database so you can query it in your server:
select * from sys.messages where message_id = 427 and language_id = 1033
When I run it on my Sql Server 2016 Express, I got the following result:
Could not load the definition for constraint ID %d in database ID %d. Run DBCC CHECKCATALOG to verify the integrity of the database.
I suggest you to debug the error message not only the number and you will get some additional information about your problem. So please log the error message too, write that here and with that we will be able to help you.
Thank you,
Morzel

THROW or RAISERROR for specific non-user defined error codes

I'm trying to test some code that traps a specific error code in SQL server. The error code is 7886.
When I try to THROW, I get:
Error number 7886 in the THROW statement is outside the valid range. Specify an error number in the valid range of 50000 to 2147483647.
When I try to RAISERROR, I get:
Error number 7886 is invalid. The number must be from 13000 through 2147483647 and it cannot be 50000.
Is there any way to raise an arbitrary error like this without actually setting up the situation to cause the error in the first place?
As said in the comments of the original question, this is impossible to do in SQL server.
:(

MS sql 2008 try catch still throws exception

I worried all day.
My label is very big - '20317302009001'.
Zlecenie is int column - so sql generates error when compare zlecenie=#label.
I tried to catch it, but still get message:
Msg 248, Level 16, State 1, Procedure label_check, Line 9
The conversion of the varchar value '20317302009001' overflowed an int column.
Who knows the answer?
Thank you!
begin TRY
if (#komponent is null) and ISNUMERIC(#label)=1
begin
set #komponent=null
if exists(select * from Rejestr_zuzycia_tkaniny where zlecenie=#label)
begin
declare #program int;
select #program=program from Rejestr_zuzycia_tkaniny where zlecenie=#label
select #komponent=komponent from Komponenty_programu where program=#program
end;
end;
end TRY
begin CATCH
set #komponent=null
end CATCH
From your code, it looks like you don't actually use zlecenie as a number, so you might want to compare by casting it as a varchar first like so:
if exists(select * from Rejestr_zuzycia_tkaniny where cast(zlecenie as varchar(20))=#label)
However, if you do need to process zlecenie as a number later on e.g. add it to something, then you might want to make it a bigint instead of int to accommodate large values.
MSDN has this to say about TRY...CATCH in T-SQL:
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.
I believe that the arithmetic overflow error might belong to the second scenario, which would explain why the CATCH block does not handle it. However, I have not been able to get any corroboration elsewhere so I suggest you do not just take my word for it.

How to catch IDL "message" with /informational flag

I have a astronomy procedure that returns an error message, but it uses the /inf flag. With this flag, none of the normal error flags are set. So how would one catch such an error? For example, I call the procedure, it prints out the informational error message, but how can I check if it outputted such a message? I see it in the console, but how can the program check for this?
I don't believe that is possible. According to the docs, when the INFORMATIONAL keyword is set, !error_state is not changed.
Set this keyword to issue informational text instead of an error. In this case, !ERROR_STATE is not set. The !QUIET system variable controls the printing of informational messages.
I think you can do the following (so long as nothing happened in between):
MESSAGE,/REISSUE_LAST
assuming you have IDL version greater than 6.0. If not, then you can just go into the routine, modify it by defining a keyword that returns the message if it was printed to the screen, otherwise it returns nothing.