Prevent ORA-01722: invalid number in Oracle - sql

I have this query
SELECT text
FROM book
WHERE lyrics IS NULL
AND MOD(TO_NUMBER(SUBSTR(text,18,16)),5) = 1
sometimes the string is something like this $OK$OK$OK$OK$OK$OK$OK, sometimes something like #P,351811040302663;E,101;D,07112018134733,07012018144712;G,4908611,50930248,207,990;M,79379;S,0;IO,3,0,0
if I would like to know if it is possible to prevent ORA-01722: invalid number, because is some causes the char in that position is not a number.
I run this query inside a procedure a process all the rows in a cursor, if 1 row is not a number I can't process any row

You could use VALIDATE_CONVERSION if it's Oracle 12c Release 2 (12.2),
WITH book(text) AS
(SELECT '#P,351811040302663;E,101;D,07112018134733,07012018144712;G,4908611,50930248,207,990;M,79379;S,0;IO,3,0,0'
FROM DUAL
UNION ALL SELECT '$OK$OK$OK$OK$OK$OK$OK'
FROM DUAL
UNION ALL SELECT '12I45678912B456781234567812345671'
FROM DUAL)
SELECT *
FROM book
WHERE CASE
WHEN VALIDATE_CONVERSION(SUBSTR(text,18,16) AS NUMBER) = 1
THEN MOD(TO_NUMBER(SUBSTR(text,18,16)),5)
ELSE 0
END = 1 ;
Output
TEXT
12I45678912B456781234567812345671

Assuming the condition should be true if and only if the 16-character substring starting at position 18 is made up of 16 digits, and the number is equal to 1 modulo 5, then you could write it like this:
...
where .....
and case when translate(substr(text, 18, 16), 'z0123456789', 'z') is null
and substr(text, 33, 1) in ('1', '6')
then 1 end
= 1
This will check that the substring is made up of all-digits: the translate() function will replace every occurrence of z in the string with itself, and every occurrence of 0, 1, ..., 9 with nothing (it will simply remove them). The odd-looking z is needed due to Oracle's odd implementation of NULL and empty strings (you can use any other character instead of z, but you need some character so no argument to translate() is NULL). Then - the substring is made up of all-digits if and only if the result of this translation is null (an empty string). And you still check to see if the last character is 1 or 6.
Note that I didn't use any regular expressions; this is important if you have a large amount of data, since standard string functions like translate() are much faster than regular expression functions. Also, everything is based on character data type - no math functions like mod(). (Same as in Thorsten's answer, which was only missing the first part of what I suggested here - checking to see that the entire substring is made up of digits.)

SELECT text
FROM book
WHERE lyrics IS NULL
AND case when regexp_like(SUBSTR(text,18,16),'^[^a-zA-Z]*$') then MOD(TO_NUMBER(SUBSTR(text,18,16)),5)
else null
end = 1;

Related

Can I use wildcards in Firebird POSITION function

I am using Firebird 2.1.
I have job order number that may have 1 or 2 alpha characters then 4 or 5 numbers then maybe a prefix with 1 alpha character and 2 numbers.
I want to extract the 4-5 digit number in the middle.
I tried the following to find the number char, but it returned 0:
POSITION('%[0-9]%',JOBHEADER.ORDERNUMBER,1) AS "FIRST NUMBER"
I am not sure if I can use wildcards with the POSITION function. I guess I could try and check the 2nd or 3rd character for a number, but I really need the wild card feature to then find the next alpha after I find the position of the first number. Or maybe there is another solution to extract the number.
I have found something simliar:
CASE WHEN SUBSTRING(ordernumber FROM 2 FOR 5) SIMILAR TO '[0-9]+'
THEN SUBSTRING(ordernumber FROM 2 FOR 5)
ELSE SUBSTRING(ordernumber FROM 3 FOR 5)
END as PROJECTNUMBER
But with the number possibly starting in the first 5 chars then a if/case statement starts getting quite big.
No you can't do this with POSITION. Position searches for the exact substring in a given string. However, with Firebird 3, you could use SUBSTRING with regular expressions to extract the value, for example:
substring(ordernumber similar '%#"[[:DIGIT:]]+#"%' escape '#')
The regular expression must cover the entire string, while the #" encloses the term to extract (the # is the explicitly defined escape symbol). You may need to use more complex patterns like [^[:DIGIT:]]*#"[[:DIGIT:]]+#"([^[:DIGIT:]]%)? to avoid edge-cases in greediness.
If you know the pattern is always 1 or 2 alpha, 4 or 5 digits you want to extract, possibly followed by 1 alpha and 2 numbers, you could also use [[:ALPHA:]]{1,2}#"[[:DIGIT:]]{4,5}#"([[:ALPHA:]][[:DIGIT:]]{1,2})?. If the pattern isn't matched null is returned.
See also:
README.substring_similar.txt
regular expression syntax
Be aware that the SQL standard regular expression syntax supported by Firebird is a bit odd, and less powerful than regular expressions common in other languages.
Using PSQL
To solve this using PSQL, under Firebird 2.1, you can use something like:
create or alter procedure extract_number(input_value varchar(50))
returns (output_value bigint)
as
declare char_position integer = 0;
declare number_string varchar(20) = '';
declare current_char char(1);
begin
while (char_position < char_length(input_value)) do
begin
char_position = char_position + 1;
current_char = substring(input_value from char_position for 1);
if ('0' <= current_char and current_char <= '9') then
begin
number_string = number_string || current_char;
end
else if (char_length(number_string) > 0) then
begin
-- switching from numeric to non-numeric, found first number occurrence in string
leave;
end
end
output_value = iif(char_length(number_string) > 0, cast(number_string as bigint), null);
end

Postresql select the first three numbers including zeros

I did not expect this to be a problem, but I'm struggling to return the first 3 numbers, including the 0's before them. In the below examples, I show a few things I've tried. I want it to return '001'. It either returns '118' or an error. It seems like every solution wants to convert them to a text, which will drop the 0's.
SELECT lpad(00118458582::text, 3, '0')
returns 118
SELECT lpad(00118458582, 3, '0')
ERROR: function lpad(integer, integer, unknown) does not exist
SELECT left(00118458582::text, 3)
returns 118
SELECT left(00118458582, 3)
ERROR: function left(integer, integer) does not exist
SELECT substring(00118458582::text, 1, 3)
returns 118
Can I get any help please? Thanks!
Your problem starts before you try to get the first 3 digits, namely that you're considering 00118458582 to be a valid INTEGER (or whatever numeric type). I mean, it's not invalid, but what happens when you run SELECT 00118458582::INTEGER? You get 118458582. Because leading zeros in those types are senseless. So you'll never have a situation as in your examples (outside of a hardcoded number with leading zeros in your query window) in your tables, because those zeros wouldn't be stored in your number-based data type fields.
So the only way to get that sort of situation is when they're string-based: SELECT '00118458582'::TEXT returns 00118458582. And at that point you can run your preferred function to get the first 3 characters, e.g. SELECT LEFT('00118458582', 3) which returns 001. But if you're planning on casting that to INTEGER or something, forget about leading zeros.
SELECT substring(00118458582::text, 1, 3)
returns 118 because it is a number 118458582 (the leading zeros are automatically dropped), that is converted to text '118458582' and it then takes the first 3 characters.
If you are trying to take the first three digits and then convert to a number you can use try:
select substring('00118458582', 1,3::numeric)
it might actually be:
select substring('00118458582', 1,3)::numeric
I don't have a way to test right now...
lpad() refers to the total length of the returned value. So I think you want:
select lpad(00118458582::text, 12, '0'::text)
If you always want exactly 3 zeros before, then just concatenate them:
select '000' || 00118458582::text

How do I return a value if the string is between 6 and 10 characters in SQL?

I have a column of data where the length of each entry varies, e.g
12345678
123
AA
12345678912345
......
I wish to return value if the string length is between 6 and 10. If it's less than 6 or greater than 10, then return a blank.
In my example I would have one value 12345678 showing and three blanks.
You can use LEN function to test length of column value and CASE to return the value you want (supposing the column name is "field"):
SELECT CASE WHEN (LEN(field) >= 6 AND LEN(field) <= 10)
THEN field
ELSE '' END as 'YourField'
FROM nameoftable
To get it without the blanks you would do:
SELECT FIELD
FROM table_name
WHERE LEN(FIELD) >= 6 AND LEN(FIELD) <= 10
If you don't mind having the output for each row presented in a new column, you could do the following:
Assume the data you have is stored in "colA" in a table called "yourTable", then:
select case when len(colA) between 6 and 10 then colA else '' end as colB from yourTable
The syntax above will work in Microsoft SQL Server. You may have to tweak to find the equivalent length and comparison functions in whichever RDMS you happen to be using.
Another issue you may face is data type conversion issues. If your colA is defined as a character field, the code above should work without issue (i.e., you'll get blanks back for values outside of your test criteria). If colA is numeric, then using '' to insert a blank may actually return 0. It's up to you to decide how you want to handle this issue.

DB2 SQL Anything left of a /

I've been working on this for days and can't seem to work it out. Basically I need return digits from a field before there is a forward slash. e.g. if the field was 1234/TEXT I want to return 1234. I can't just use left fieldname 4 as the digits vary in left e.g. 12345/TEXT, so it needs to be anything left of the forward slash. Now in the World of MS Access, it is something like this - and it works
Left(TABLE!FIELD,InStr(1,TABLE!FIELD,"/")-1)
However, how do I convert this to be used in an IBM\DB2 system? The DB2 SQL seems somewhat different to 'normal' SQL.
Thanks!
Rather than INSTR, maybe LOCATE
LOCATE(char, string)
char is the search term
string is the string being searched
You can achieve this by combining LOCATE with SUBSTR;
Locate information
Substring information
Cheat sheet (for this example);
SUBSTRING('FIELD','START POSITION', 'LENGTH')
LOCATE('SEARCH STRING', 'SOURCE STRING')
SUBSTRING lets you retrieve specific characters from a string, i.e.;
AFIELD = 'Hello'
SUBSTRING(AFIELD,4,2)
Result = 'lo' (position 4 and 5 of Hello)
LOCATE returns the position of the first character of the search string it finds as a number, i.e.;
AFIELD = 'Hello'
LOCATE('ello', AFIELD)
Result = 2 (it starts at position 2)
So you can combine these to do what you want, example;
XTABLE has 1 column called ACOL with the following values in it;
123467/ABCD
1321/ABDD
1123467/ABCD
To just retrieve the numbers;
SELECT SUBSTRING(ACOL,1, LOCATE('/',ACOL)-1)
FROM XRDK/XTABLE
Result;
123467
1321
1123467
What are we doing?
SUBSTRING(
ACOL,
1,
LOCATE('/',ACOL)-1
)
SUBSTRING(
Field ACOL,
Starting at position 1,
Length; using locate set this to where I find a '/' and subtract 1 from the
resulting postion (without the -1 you'd have the / on the end)
)
Try this
SELECT SUBSTRING(CAST (ROUND(COLUMN,2) AS DECIMAL(6,2)), 0, locate('/',CAST (ROUND(COLUMN,2) AS DECIMAL(6,2))))
FROM TABLE

Check if number in varchar2 is greater than n

I've got a Varchar2 field which usually holds two alphabetic characters (such as ZH, SZ, AI,...). Let's call it FOO.
Certain datasets save A or A1 - A9 into the same field. I need to select all rows except exactly those.
I used the function substr to separate the number from the A. So far so good, < or > don't seem to work correctly with the "number-string".
How can I achieve this without converting it to a number? Is there an easier solution?
I haven't found anything on the internet and I reached my limit trying it myself.
This is my WHERE clause so far:
WHERE (substr(FOO, 0, 1) != 'A'
or (substr(FOO, 0, 1) = 'A' AND substr(FOO, 1, 1) > '9'));
It returns all the rows without restrictions.
The only solution I found:
WHERE (FOO NOT IN ('A', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'));
But this is not optimal if, somewhere in the future, there will be A1 - A50. I would have to add 51 strings to my WHERE clause. And, since the query is in source code, also the code readability would get worse.
The solution should work on ORACLE and SQL Server.
Thanks in advance
(substr(FOO, 0, 1) = (substr(FOO, 1, 1) - Oracle starts with 1 (not 0).
So you should use substr(FOO, 2, 1) to get the second symbol.
However, it won't work in SQL Server which has SUBSTRING (not SUBSTR).
if you're ready to use different approaches in the different DBs you can also try regular expressions:
Oracle
where not regexp_like(foo, '^A[1-9]{1,3}$')
^ begining of the string
$ end of the string
[1-9] any digit from 1 to 9
{1,3} repeat the previous expression 1,2 or 3 times
Examples of FOOs which match / not match '^A[1-9]{1,3}$'
a123 -- may match / may not (depending on NLS settings regarding case sensitivity)
A123 -- match (the first symbol is 'A', the others are 3 digits)
A123b -- doesn't match (the last symbol should be a digit)
A1234 -- doesn't match (there should be 1,2 or 3 digits an the end)
A12 -- match
A1 -- match
SQL Server
REGEXP_LIKE conversion in SQL Server T-SQL
If your requirement is to include all alphabetic values except 'A' alone, consider using a LIKE expression so that it will work with any ANSI-compliant DBMS:
WHERE FOO <> 'A' AND FOO NOT LIKE '%[^A-Z]%'