MS sql 2008 try catch still throws exception - sql

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.

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

SQL Server Spatial Select Where STContains

I'm working on a spatial database SQL Server and am having a hard time querying a row where the geography contains a given lat/long.
I'm able to get this query to work:
DECLARE #polygon Geography;
select #polygon = (
select
geog4269
from census_tracts
WHERE namelsad10 = 'Census Tract 9801.02'
);
set #polygon = #polygon.ReorientObject();
select #polygon.STContains(
geography::Point(18.4102591, -66.0732014, 4269)
);
However, I want to be able to select the row that contains a given lat/long with something like the following:
select
*
from census_tracts
WHERE geog4269.ReorientObject().STContains(
geography::Point(18.4102591, -66.0732014, 4269)
) = 1
I'm getting a .NET Framework exception when I run that saying to use MakeValid to avoid it, but adding .MakeValid() doesn't fix the issue.
This is the exception message:
Msg 6522, Level 16, State 1, Line 1
A .NET Framework error occurred during execution of user-defined routine or aggregate "geography":
System.ArgumentException: 24144: This operation cannot be completed because the instance is not valid. Use MakeValid to convert the instance to a valid instance. Note that MakeValid may cause the points of a geometry instance to shift slightly.
System.ArgumentException:
at Microsoft.SqlServer.Types.SqlGeography.ThrowIfInvalid()
at Microsoft.SqlServer.Types.SqlGeography.ReorientObject()
.
When I use the following query:
select
*
from census_tracts
WHERE geog4269.MakeValid().ReorientObject().STContains(
geography::Point(18.4102591, -66.0732014, 4269)
) = 1
The geographies don't get reoriented (every geography says it contains all points).
Has anyone run into something similar before or can point out where I'm going wrong? Thanks for the help!
My guess is for some geographies MakeValid also fixes the orientation of the polygons. If the polygons is invalid, MakeValid has to make a guess about what was the intended shape, and fix it using some heuristics. The results may vary depending on what exactly was wrong with the data - and sometime garbage in, garbage out applies.
I would avoid using both MakeValid() and ReorientObject() in the query. This is both error prone, and slow (as it prevents spatial index usage).
Instead, fix the actual data by updating the geographies to be the intended ones.
Quick and dirty way is to invert only those that need inverting, something like
update census_tracts
set geog4269 =
IF (geog4269.MakeValid().STArea() < 1e14, -- is it small?
geog4269.MakeValid(),
geog4269.MakeValid().ReorientObject());

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.

Selecting large sequence value into global variable not working in PL/SQL code

I have a script where I would like to have a global variable to store a transaction number for later use. The code is working fine on one schema where the sequence value that I'm fetching is relatively low. It is not working on another schema with a higher sequence value where I get a "Numeric Overflow". If I change that sequence value to a lower number it is working as well but that is not an option.
VAR TRANSACTIONNR NUMBER;
BEGIN
--Works with NEXTVAL being around 946713241
--Doesn't work with NEXTVAL being around 2961725541
SELECT MY_SEQUENCE.NEXTVAL INTO :TRANSACTIONNR FROM DUAL;
MY_PACKAGE.STARTTRANSACTION(:TRANSACTIONNR);
END;
/
-- SQL Statements
BEGIN
MY_PACKAGE.ENDTRANSACTION;
MY_PACKAGE.DO_SOMETHING(:TRANSACTIONNR);
END;
/
What is also working is selecting the sequence into a variable declared in the DECLARE block:
DECLARE
TRANSACTIONNR NUMBER;
BEGIN
SELECT MY_SEQUENCE.NEXTVAL INTO TRANSACTIONNR FROM DUAL;
MY_PACKAGE.STARTTRANSACTION(TRANSACTIONNR);
END;
/
But that means I won't be able to reuse it in the block at the end. Setting the size of the number is not possible.
VAR TRANSACTIONNR NUMBER(15)
is not valid.
Any ideas what I could try or other ways to store global state?
On further investigation this looks like it might be a SQL Developer bug (making assumptions about what you're doing again, of course...). I can get the same error with:
VAR TRANSACTIONNR NUMBER;
BEGIN
SELECT 2961725541 INTO :TRANSACTIONNR FROM DUAL;
END;
/
It appears that SQL Developer's NUMBER is limited to 2^31, which isn't the case normally for Oracle.
A possibly workaround is to use BINARY_FLOAT to store the value, but you'll run into precision problems eventually (not sure where, but looks OK up to 2^53-ish), and you'll need to cast() it back to NUMBER when using it.
VAR TRANSACTIONNR BINARY_DOUBLE;
BEGIN
SELECT 2961725541 INTO :TRANSACTIONNR FROM DUAL;
-- dbms_output.put_line(cast(:TRANSACTIONNR as NUMBER)); -- null for some reason
END;
/
...
BEGIN
dbms_output.put_line(cast(:TRANSACTIONNR as NUMBER));
END;
/
For some reason I don't seem to be able to refer to the bind variable again in the anonymous block I set it in - it's null in the commented-out code - which seems to be another SQL Developer quirk, regardless of the var type; but as you were doing so in your code, I may again have assumed too much...
Original answer for posterity, as it may still be relevant in other circumstances...
Presumably you're doing something to end the current transaction, e.g. a commit in endtransaction; otherwise you could just refer to my_sequence.currval in the do_something call. A number variable is fine for this size of number though, it won't have a problem with a sequence that size, and it won't make any difference that it is from a sequence rather than manually assigned. I don't think the problem is with the storage or the sequence.
It seems rather more likely that the error is coming from one of the package procedures you're calling, though I can't quite imagine what you might be doing with it; something like this will cause the same error though:
create sequence my_sequence start with 2961725541;
create package my_package as
procedure starttransaction(v_num number);
procedure endtransaction;
procedure do_something(v_num number);
end my_package;
/
create package body my_package as
procedure starttransaction(v_num number) is
begin
dbms_output.put_line('starttransaction(): ' || v_num);
for i in 1..v_num loop
null;
end loop;
end starttransaction;
procedure endtransaction is
begin
dbms_output.put_line('endtransaction()');
end endtransaction;
procedure do_something(v_num number) is
begin
dbms_output.put_line('do_something(): ' || v_num);
end do_something;
end my_package;
/
When your code is run against that it throws your error:
BEGIN
*
ERROR at line 1:
ORA-01426: numeric overflow
ORA-06512: at "STACKOVERFLOW.MY_PACKAGE", line 6
ORA-06512: at line 5
endtransaction()
do_something():
Note the error is reported against line 6 in the package, which is the for ... loop line, not from the assignment in your anonymous block.
Looping like that would be an odd thing to do of course, but there are probably other ways to generate that error. The breakpoint for it working is if the nextval is above 2^31. If I start the sequence with 2147483647 it works, with 2147483648 it errors.
I'm assuming you are actually getting an ORA-01426 from the original question; if it's actually a ORA-1438 or ORA-06502 then it's easier to reproduce, by trying to assign the value to a number(9) column or variable. 'Numeric overflow' is pretty specific though.

Why did this the SQL parser execute this illegal syntax?

I have a stored procedure in this form:
ALTER PROCEDURE [dbo].[fooBar]
(
)
AS
BEGIN
-- etc
RETURN #Success
END
It had been working perfectly with BEGIN and END in it before, but after changing something and reverting it back, it refused to execute, pointing to a syntax error at the last END (removing that next pointed to a syntax error at the first IF/BEGIN/.... statement inside the procedure, and thus begins your wild goose chase).
Looking at the MSDN official documentation for the syntax, BEGIN and END used in this way to envelope a stored procedure is illegal. (Removing the BEGIN and END solved the problem)
Question: Why did this happen?
Did the compiler skip over this BEGIN and END initially and later discover it? Are there some things that the SQL compiler ignores? Is it legacy? Is it just finicky? Am I missing a hotfix?
This is SQL Server 10.50.1617
BEGIN/END are perfectly valid for a stored procedure. This is explicitly set in the grammar at the documentation page you linked to.
It is the parentheses that are not allowed.