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

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

Related

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

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.

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

How to execute SQL Script which may or may not return data?

This is an extension of an old question of mine where the answer wasn't quite what I was asking. What I'm doing is executing SQL Script on an MS SQL Server database. This script may or may not return any recordsets. The problem is that the way that ADO components work, at least to my knowledge, I can only explicitly request one or the other.
If I know a query will return data, I use TADOQuery.Open
If I know a query will not return data, I use TADOConnection.Execute
If I don't know whether query will return data or not... ???
How can I execute any query and read the response to determine whether it has any recordsets or not so I can read that recordset?
What I've tried:
Calling TADOQuery.Open, but raises exception if there's no recordset
Calling TADOQuery.ExecSql, but never returns any data
Calling TADOConnection.Execute, but never returns any data
Using Option 3 and reverting to Option 1 on exceptions, but this is double the work (script files over 38,000 lines) and kinda nasty.
Using TADOCommand.Execute, but keeps raising "Parameter object is improperly defined. Inconsistent or incomplete information was provided" on creating some stored procedures (which otherwise don't happen when using TADOConnection.Execute).
Calling TADOConnection.Execute overload which returns _Recordset, but then the TADOConnection.Errors returns empty (and I depend on this).
Just as some background, the purpose is to implement something like the SQL Query tool in the SQL Server Management Studio. It allows you to execute any SQL script, and if it returns any recordsets, it displays them, otherwise it just displays output messages. The tool I'm working on automatically breaks SQL Script at GO statements, and executes each individual block separately. Some of those blocks might return data, and others might not. It's already obvious that I cannot make this determination prior to execution, so I'm looking for a way to go ahead with the execution and observe the result. TADOConnection.Execute provides some useful information, including the Errors (or output messages).
As of now, the only option I have is to supply an option in the user interface to allow the user to choose which type of execution to use - but this is what I'm trying to eliminate.
EDIT
The TADOCommand.Execute method is the closest to what I want. However, it fails on some bits of script which otherwise work perfectly fine using TADOConnection.Execute. See #5 above in "What I've tried". I almost wrote that as my answer, until I found this error happens on almost everything.
EDIT
After posting my answer below, I then came to learn that the Errors property no longer returns anything when I use this other overload of Execute. See #6 above in "What I've tried".
Calling...
ADOConnection1.Execute('select * from something', cmdText, []);
...does not return anything in ADOConnection1.Errors, whereas...
var
R: Integer;
begin
ADOConnection1.Execute('select * from something', R);
...does return messages in ADOConnection1.Errors, which is what I need, but yet, doesn't return any recordsets.
EDIT: Not the right solution
I discovered my solution finally after digging even deeper. The answer is to use the TADOConnection.Execute() overload which supports returning the recordset:
function TADOConnection.Execute(const CommandText: WideString;
const CommandType: TCommandType = cmdText;
const ExecuteOptions: TExecuteOptions = []): _Recordset;
Then, just assign the resulting _Recordset to the Recordset property of supported dataset components.
var
RS: _Recordset;
begin
RS := ADOConnection1.Execute('select * from something', cmdText, []);
if Assigned(RS) then begin
ADODataset1.Recordset:= RS;
...
end;
end;
The downside is that you cannot use the other overload which supports returning the RowsAffected. Also, nothing is returned in the Errors property of the TADOConnection when using this overload version of Execute, whereas the other one does. But that other doesn't return a recordset.

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.

PL/SQL Validate/Insert/Update Status - retval vs. exception

I'm always looking to improve and applying best practices. I read quite a bit about refactoring in the last weeks. I have to work with a lot of awful code and I produced some not so nice stuff too but I'm trying to change that. Thats no problem for most languages but I'm pretty new to PL/SQL so I just copied the style of the already written code.
After reading some tutorials I realized that a lot of our code is pretty much more C style code using retval instead exceptions etc.
We have a lot of functions like open cursor, loop through it, validate the data, trim it or make some string manipulation and insert it into another table, update the status etc. I wonder what a best practice solution would look like on something like this. Atm most functions look like this:
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
retval := plausibilty_check(r_ABC);
IF (retval = STATUS_OK) THEN
retval := convert_ABC_TO_XYZ(r_ABC, r_XYZ);
END IF;
IF (retval = STATUS_OK) THEN
retval := insert_XYZ(r_XYZ);
END IF;
retval := update_ABC(r_ABC.PK_Id, retval);
END LOOP;
If i was using exceptions I guess I had to raise them inside the functions so I can handle them in the main function, if not everyone would have to crawl to every sub function to understand the program and where the updates happen etc. So I guess I would have to use another PL/SQL block inside the loop? Like:
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
BEGIN
plausibilty_check(r_ABC);
convert_ABC_TO_XYZ(r_ABC, r_XYZ);
insert_XYZ(r_XYZ);
update_ABC(r_ABC.PK_Id, STATUS_OK);
EXCEPTION
WHEN ERROR_CODE_XYZ THEN
update_ABC(r_ABC.PK_Id, ERROR_CODE_XYZ);
END
END LOOP;
I guess that function handles a pretty common problem but I still havn't found any tutorial covering something like this. Maybe someone more experienced with PL/SQL might gimme a hint what a best practice on a task like that would look like.
I like to use exceptions to jump out of a block of code if an exceptional event (e.g. an error) occurs.
The problem with the "retval" method of detecting error conditions is that it introduces a second layer of semantics on what a function is and for.
In principle, a function should be used to perform a calculation and return a result; in this sense, a function doesn't do anything, i.e. it makes no changes to any state - it merely returns a value.
If it cannot for some reason calculate that value, that would be an exceptional circumstance, so I'd want the function to raise an exception so that the calling program will not blindly continue on its merry way, thinking it got a valid value from the function.
On the other hand, a procedure is a method by which action is done - something is changed, something is validated, or something is sent. The normal expected path is that the procedure is executed, it does its thing, then it finishes. If an error occurs, I want it to raise an exception so that the calling program will not blindly continue thinking that the procedure has successfully done its thing.
Thus, the original intent of the fundamental difference between "procedures" and "functions" is preserved.
In languages like C, there are no procedures - everything is a function in a sense (even functions that return "void") - but in addition, there is no real concept of an "exception" - so these semantics don't apply. It's for this reason that the C style of returning an error/success flag don't translate well into languages like PL/SQL.
In your example, I'd consider doing it something like this:
BEGIN
LOOP
FETCH c_ABC INTO r_ABC;
EXIT WHEN c_ABC%NOTFOUND;
IF record_is_plausible(r_ABC) THEN
r_XYZ := convert_ABC_TO_XYZ(r_ABC);
insert_or_update_XYZ(r_XYZ);
ELSE
update_as_implausible(r_ABC);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
-- log the error or something, then:
RAISE;
END;
So where the semantics of the operation is doing some validation or something, and returning a result, I converted plausibilty_check into a function record_is_plausible that returns a boolean.
I'd pull the call to update_ABC out of the BEGIN block and make it common at the bottom of the loop, as in:
DECLARE
nFinal_status NUMBER;
BEGIN
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
nFinal_status := nSTATUS_OK;
BEGIN
plausibilty_check(r_ABC);
convert_ABC_TO_XYZ(r_ABC, r_XYZ);
insert_XYZ(r_XYZ);
EXCEPTION
WHEN excpERROR_CODE_XYZ THEN
nFinal_status := nERROR_CODE_XYZ;
END;
update_ABC(r_ABC.PK_Id, nFinal_status);
END LOOP;
END;
You might want to have each of the procedures throw its own exception so you could more easily identify where the issue(s) are coming from - or use different exceptions/error codes for each possible issue (for example, the plausibility check might raise different exceptions depending on what implausible condition it found). However, in my experience plausibility checks will often uncover multiple conditions (if the data's bad it's often really bad :-), so it might be nice to tabularize the errors and relate them to the base ABC data through a foreign key, thus allowing each individual error to be identified with just one pass through the data. Then the 'status' field on ABC becomes moot; you either have errors associated with the ABC row or you don't. If errors exist, do whatever is needed. If no errors, proceed with 'normal' processing.
Just a thought.
Share and enjoy.