check if "it's a number" function in Oracle - oracle

I'm trying to check if a value from a column in an oracle (10g) query is a number in order to compare it. Something like:
select case when ( is_number(myTable.id) and (myTable.id >0) )
then 'Is a number greater than 0'
else 'it is not a number'
end as valuetype
from table myTable
Any ideas on how to check that?

One additional idea, mentioned here is to use a regular expression to check:
SELECT foo
FROM bar
WHERE REGEXP_LIKE (foo,'^[[:digit:]]+$');
The nice part is you do not need a separate PL/SQL function. The potentially problematic part is that a regular expression may not be the most efficient method for a large number of rows.

Assuming that the ID column in myTable is not declared as a NUMBER (which seems like an odd choice and likely to be problematic), you can write a function that tries to convert the (presumably VARCHAR2) ID to a number, catches the exception, and returns a 'Y' or an 'N'. Something like
CREATE OR REPLACE FUNCTION is_number( p_str IN VARCHAR2 )
RETURN VARCHAR2 DETERMINISTIC PARALLEL_ENABLE
IS
l_num NUMBER;
BEGIN
l_num := to_number( p_str );
RETURN 'Y';
EXCEPTION
WHEN value_error THEN
RETURN 'N';
END is_number;
You can then embed that call in a query, i.e.
SELECT (CASE WHEN is_number( myTable.id ) = 'Y' AND myTable.id > 0
THEN 'Number > 0'
ELSE 'Something else'
END) some_alias
FROM myTable
Note that although PL/SQL has a boolean data type, SQL does not. So while you can declare a function that returns a boolean, you cannot use such a function in a SQL query.

Saish's answer using REGEXP_LIKE is the right idea but does not support floating numbers. This one will ...
Return values that are numeric
SELECT foo
FROM bar
WHERE REGEXP_LIKE (foo,'^-?\d+(\.\d+)?$');
Return values not numeric
SELECT foo
FROM bar
WHERE NOT REGEXP_LIKE (foo,'^-?\d+(\.\d+)?$');
You can test your regular expressions themselves till your heart is content at http://regexpal.com/ (but make sure you select the checkbox match at line breaks for this one).

This is a potential duplicate of Finding rows that don't contain numeric data in Oracle. Also see: How can I determine if a string is numeric in SQL?.
Here's a solution based on Michael Durrant's that works for integers.
SELECT foo
FROM bar
WHERE DECODE(TRIM(TRANSLATE(your_number,'0123456789',' ')), NULL, 'number','contains char') = 'number'
Adrian Carneiro posted a solution that works for decimals and others. However, as Justin Cave pointed out, this will incorrectly classify strings like '123.45.23.234' or '131+234'.
SELECT foo
FROM bar
WHERE DECODE(TRIM(TRANSLATE(your_number,'+-.0123456789',' ')), NULL, 'number','contains char') = 'number'
If you need a solution without PL/SQL or REGEXP_LIKE, this may help.

You can use the regular expression function 'regexp_like' in ORACLE (10g)as below:
select case
when regexp_like(myTable.id, '[[:digit:]]') then
case
when myTable.id > 0 then
'Is a number greater than 0'
else
'Is a number less than or equal to 0'
end else 'it is not a number' end as valuetype
from table myTable

I'm against using when others so I would use (returning an "boolean integer" due to SQL not suppporting booleans)
create or replace function is_number(param in varchar2) return integer
is
ret number;
begin
ret := to_number(param);
return 1; --true
exception
when invalid_number then return 0;
end;
In the SQL call you would use something like
select case when ( is_number(myTable.id)=1 and (myTable.id >'0') )
then 'Is a number greater than 0'
else 'it is not a number or is not greater than 0'
end as valuetype
from table myTable

This is my query to find all those that are NOT number :
Select myVarcharField
From myTable
where not REGEXP_LIKE(myVarcharField, '^(-)?\d+(\.\d+)?$', '')
and not REGEXP_LIKE(myVarcharField, '^(-)?\d+(\,\d+)?$', '');
In my field I've . and , decimal numbers sadly so had to take that into account, else you only need one of the restriction.

How is the column defined? If its a varchar field, then its not a number (or stored as one). Oracle may be able to do the conversion for you (eg, select * from someTable where charField = 0), but it will only return rows where the conversion holds true and is possible. This is also far from ideal situation performance wise.
So, if you want to do number comparisons and treat this column as a number, perhaps it should be defined as a number?
That said, here's what you might do:
create or replace function myToNumber(i_val in varchar2) return number is
v_num number;
begin
begin
select to_number(i_val) into v_num from dual;
exception
when invalid_number then
return null;
end;
return v_num;
end;
You might also include the other parameters that the regular to_number has. Use as so:
select * from someTable where myToNumber(someCharField) > 0;
It won't return any rows that Oracle sees as an invalid number.
Cheers.

CREATE OR REPLACE FUNCTION is_number(N IN VARCHAR2) RETURN NUMBER IS
BEGIN
RETURN CASE regexp_like(N,'^[\+\-]?[0-9]*\.?[0-9]+$') WHEN TRUE THEN 1 ELSE 0 END;
END is_number;
Please note that it won't consider 45e4 as a number, But you can always change regex to accomplish the opposite.

#JustinCave - The "when value_error" replacement for "when others" is a nice refinement to your approach above. This slight additional tweak, while conceptually the same, removes the requirement for the definition of and consequent memory allocation to your l_num variable:
function validNumber(vSomeValue IN varchar2)
return varchar2 DETERMINISTIC PARALLEL_ENABLE
is
begin
return case when abs(vSomeValue) >= 0 then 'T' end;
exception
when value_error then
return 'F';
end;
Just a note also to anyone preferring to emulate Oracle number format logic using the "riskier" REGEXP approach, please don't forget to consider NLS_NUMERIC_CHARACTERS and NLS_TERRITORY.

well, you could create the is_number function to call so your code works.
create or replace function is_number(param varchar2) return boolean
as
ret number;
begin
ret := to_number(param);
return true;
exception
when others then return false;
end;
EDIT: Please defer to Justin's answer. Forgot that little detail for a pure SQL call....

You can use this example
SELECT NVL((SELECT 1 FROM DUAL WHERE REGEXP_LIKE (:VALOR,'^[[:digit:]]+$')),0) FROM DUAL;

Function for mobile number of length 10 digits and starting from 9,8,7 using regexp
create or replace FUNCTION VALIDATE_MOBILE_NUMBER
(
"MOBILE_NUMBER" IN varchar2
)
RETURN varchar2
IS
v_result varchar2(10);
BEGIN
CASE
WHEN length(MOBILE_NUMBER) = 10
AND MOBILE_NUMBER IS NOT NULL
AND REGEXP_LIKE(MOBILE_NUMBER, '^[0-9]+$')
AND MOBILE_NUMBER Like '9%' OR MOBILE_NUMBER Like '8%' OR MOBILE_NUMBER Like '7%'
then
v_result := 'valid';
RETURN v_result;
else
v_result := 'invalid';
RETURN v_result;
end case;
END;

Note that regexp or function approaches are several times slower than plain sql condition.
So some heuristic workarounds with limited applicability make sence for huge scans.
There is a solution for cases when you know for sure that non-numeric values would contain some alphabetic letters:
select case when upper(dummy)=lower(dummy) then '~numeric' else '~alpabetic' end from dual
And if you know some letter would be always present in non-numeric cases:
select case when instr(dummy, 'X')>0 then '~alpabetic' else '~numeric' end from dual
When numeric cases would always contain zero:
select case when instr(dummy, '0')=0 then '~alpabetic' else '~numeric' end from dual

if condition is null then it is number
IF(rtrim(P_COD_LEGACY, '0123456789') IS NULL) THEN
return 1;
ELSE
return 0;
END IF;

Here's a simple method which :
does not rely on TRIM
does not rely on REGEXP
allows to specify decimal and/or thousands separators ("." and "," in my example)
works very nicely on Oracle versions as ancient as 8i (personally tested on 8.1.7.4.0; yes, you read that right)
SELECT
TEST_TABLE.*,
CASE WHEN
TRANSLATE(TEST_TABLE.TEST_COLUMN, 'a.,0123456789', 'a') IS NULL
THEN 'Y'
ELSE 'N'
END
AS IS_NUMERIC
FROM
(
-- DUMMY TEST TABLE
(SELECT '1' AS TEST_COLUMN FROM DUAL) UNION
(SELECT '1,000.00' AS TEST_COLUMN FROM DUAL) UNION
(SELECT 'xyz1' AS TEST_COLUMN FROM DUAL) UNION
(SELECT 'xyz 123' AS TEST_COLUMN FROM DUAL) UNION
(SELECT '.,' AS TEST_COLUMN FROM DUAL)
) TEST_TABLE
Result:
TEST_COLUMN IS_NUMERIC
----------- ----------
., Y
1 Y
1,000.00 Y
xyz 123 N
xyz1 N
5 rows selected.
Granted this might not be the most powerful method of all; for example ".," is falsely identified as a numeric. However it is quite simple and fast and it might very well do the job, depending on the actual data values that need to be processed.
For integers, we can simplify the Translate operation as follows :
TRANSLATE(TEST_TABLE.TEST_COLUMN, 'a0123456789', 'a') IS NULL
How it works
From the above, note the Translate function's syntax is TRANSLATE(string, from_string, to_string). Now the Translate function cannot accept NULL as the to_string argument.
So by specifying 'a0123456789' as the from_string and 'a' as the to_string, two things happen:
character a is left alone;
numbers 0 to 9 are replaced with nothing since no replacement is specified for them in the to_string.
In effect the numbers are discarded. If the result of that operation is NULL it means it was purely numbers to begin with.

Related

Alternative to REGEXP_LIKE due to regular expression too long

I am getting an ORA-12733: regular expression too long error when trying to find if certain ids already inside the database.
regexp_like (','||a.IDs||',',',('||replace(b.IDs,',','|')||'),')
a.IDs and b.IDs are in a format of something like id=16069,16070,16071,16072,16099,16100.
i will replace comma with | in b so it will tell me if any of the number is matched. The length of both a.IDs and b.IDs might vary from different queries.
Oracle regexp_like limit is only 512. Anyone know if other possible solutions?
Why on earth do you store list of numbers as string?
Anyway, one possible solution is this one. Create TYPE and FUNCTION like this:
CREATE OR REPLACE TYPE NUMBER_TABLE_TYPE AS TABLE OF NUMBER;
CREATE OR REPLACE FUNCTION SplitArray(LIST IN VARCHAR2, Separator IN VARCHAR2) RETURN NUMBER_TABLE_TYPE IS
OutTable NUMBER_TABLE_TYPE;
BEGIN
IF LIST IS NULL THEN
RETURN NULL;
ELSE
SELECT REGEXP_SUBSTR(LIST, '[^'||Separator||']+', 1, LEVEL)
BULK COLLECT INTO OutTable
FROM dual
CONNECT BY REGEXP_SUBSTR(LIST, '[^'||Separator||']+', 1, LEVEL) IS NOT NULL;
END IF;
IF OutTable.COUNT > 0 THEN
RETURN OutTable;
ELSE
RETURN NULL;
END IF;
END SplitArray;
Then you query for a single number as this:
WHERE 16071 MEMBER OF SplitArray(a.IDs, ',')
or for several numbers as this:
WHERE SplitArray(b.IDs, ',') SUBMULTISET OF SplitArray(a.IDs, ',')
Have a look at Multiset Conditions

Oracle stored procedure - CASE v_Variable WHEN 'value' OR 'value' THEN

Here is a bit of pseudocode to explain what I am looking to do:
CREATE OR REPLACE PROCEDURE sproc
(
v_input IN VARCHAR2 DEFAULT NULL
, refCursor OUT SYS_REFCURSOR
)
AS
CASE v_input
WHEN 'A' THEN
OPEN refCursor FOR
SELECT ... FROM ...;
WHEN 'B' OR 'C'
OPEN refCursor FOR
SELECT ... FROM ...;
END CASE;
END sproc;
How can I accomplish this 'WHEN 'B' OR 'C' clause?
You can only use one condition per when using that syntax. But, if you include the conditional variable in the when you can specify more values:
CASE
WHEN v_input = 'A' THEN
OPEN refCursor FOR
SELECT ... FROM ...;
WHEN v_input IN ('B','C')
OPEN refCursor FOR
SELECT ... FROM ...;
END CASE;
The distinction (as pointed out by xQbert) between simple case statements and searched case statements is specified in the SQL-92 (or later) standard. While the actual reasoning could probably be found with enough work, my conjecture is this:
I think it was about consistency: everywhere else in SQL, Boolean operators like or and and require complete comparisons such as x = 1 or y = 2. The Boolean operators can never be used strictly on operands, such as x = (1 or 3). In the simple case syntax, the comparison is broken up, which forces a choice: they could either 1) only support equality (no in, <>, <, etc.; no Boolean operators) or 2) come up with a special syntax for operators that only applies within simple case statements (and, probably, come up with a nomenclature other than "simple").
You can use IF and IN statement
create or replace procedure sproc(
v_input in varchar2 default null ,
refcursor out sys_refcursor )
as
begin
if v_input = 'A' then
open refcursor for select ... from ...;
elsif v_input in ('B','C') then
open refcursor for select ... from ...;
end if;
end sproc;
/

Oracle using the like comparison taken from function in value

I am trying to create a simple function that takes in 3 parameters, 2 numbers and a string. I have written the function but am not getting the expected results from a simple select statement when using the LIKE comparison for the string.
The select from the function below returns no rows when executed with the string input value set to ebts, but if I run this as a standalone select state it returns 2 rows which what I would expect. Have used dbms output to determine if whitespace were being passed but all looks OK.
CREATE OR REPLACE FUNCTION OPC_OP.sitezone_exists
(in_site_id IN NUMBER, in_zone_id IN NUMBER, in_mod VARCHAR2)
RETURN NUMBER
IS
v_count_rec NUMBER;
v_return NUMBER;
v_mod VARCHAR2(4) := in_mod;
BEGIN
SELECT COUNT(*)
INTO v_count_rec
FROM AW_ACTIVE_ALARMS
WHERE AW_ACTIVE_ALARMS.site_id = in_site_id
AND AW_ACTIVE_ALARMS.zone_id = in_zone_id
AND AW_ACTIVE_ALARMS.module LIKE 'v_mod%';
IF v_count_rec > 0
THEN
DBMS_OUTPUT.PUT_LINE('count'||v_count_rec||'=========='||v_mod||'=============');
v_return:= 1;
RETURN (v_return);
ELSE
DBMS_OUTPUT.PUT_LINE('count'||v_count_rec||'=========='||v_mod||'=============');
v_return:= 0;
RETURN (v_return);
END IF;
END sitezone_exists;
When passing in values 12, 12, ebts the output displayed is:
count 0 ==========ebts=============
RetVal = 0
If I run the same select subtituting only passing in the above values the query returns 2 rows - I have removed the like clause of the function and it then returns 2 rows, any idea why the like part of clause is failing to match with rows.
You are trying to match a string literal with this:
AND AW_ACTIVE_ALARMS.module LIKE 'v_mod%';
Change it to:
AND AW_ACTIVE_ALARMS.module LIKE v_mod||'%';
MarioAna has the right answer, IMO, but as an aside (which is too long for a comment), your function can be better written.
You're duplicating the dbms_output code, plus it's considered best practice to have a single RETURN in a function (although I would argue that more might be ok - one in the body and one per exception in the exception block...), so you could rewrite it as:
CREATE OR REPLACE FUNCTION OPC_OP.sitezone_exists
(in_site_id IN NUMBER, in_zone_id IN NUMBER, in_mod VARCHAR2)
RETURN NUMBER
IS
v_count_rec NUMBER;
v_return NUMBER;
v_mod VARCHAR2(4) := in_mod;
BEGIN
SELECT COUNT(*)
INTO v_count_rec
FROM AW_ACTIVE_ALARMS aaa
WHERE aaa.site_id = in_site_id
AND aaa.zone_id = in_zone_id
AND aaa.module LIKE v_mod||'%';
DBMS_OUTPUT.PUT_LINE('count '||v_count_rec||'=========='||v_mod||'=============');
IF v_count_rec > 0 THEN
v_return := 1;
ELSE
v_return:= 0;
END IF;
RETURN (v_return);
END sitezone_exists;
/

Trigger with a function Oracle

I need to make a trigger to insert 'A' in case of a approved student and 'R' for a not approved student. I need to use a function that returns 'A' or 'R'. Here is my function:
create or replace function check_grade
(grade in number, frequency in number) return varchar2
as
result varchar2(1) default '';
begin
if(grade>=6) and (frequency>=0.75)then
result := 'A';
else
result := 'R';
end if;
return result;
end;
And here is my trigger:
create or replace trigger situation
before
insert on student
for each row
begin
select check_grade(grade, frequency)
into : new.situation
from dual;
end;
When I try to execute I always get an error. I don't have idea in what i can do!
User-defined functions are allowed in triggers, and your function looks OK.
One definite problem with your trigger is that it mentions grade and frequency, which are variable names, and they haven't been declared. I'll assume you probably meant to use the inserted column values instead: :new.grade and :new.frequency.
Also, I've always set "new" values using simple variable assignment (:new.value := blah instead of select blah into :new.value from dual). Your way may work (or may not - I don't know), but the simple variable assignment syntax is a lot shorter and it's easy enough to read.
So try replacing these three lines:
select check_grade(grade, frequency)
into : new.situation
from dual;
... with this one line:
:new.situation := check_grade(:new.grade, :new.frequency);
If this doesn't work then please update the question and replace "I always get an error" with I get error ORA-<actual-error-number>. Include the stack trace, which will show if the function or the trigger is throwing.
The function isn't needed here; it can be replaced by a CASE expression:
create or replace trigger situation
before insert on student
for each row
begin
:new.situation := CASE
WHEN grade >= 6 and frequency >= 0.75 THEN 'A'
ELSE 'R';
END;
end situation;

"Boolean" parameter for Oracle stored procedure

I'm aware that Oracle does not have a boolean type to use for parameters, and am currently taking in a NUMBER type which would have 1/0 for True/False (instead of the 'Y'/'N' CHAR(1) approach).
I'm not a very advanced Oracle programmer, but after doing some digging and reading some ASKTOM posts, it seems like you can restrict a field using a format for the column like:
MyBool NUMBER(1) CHECK (MyBool IN (0,1))
Is there a way to apply the same sort of a check constraint to an input parameter to a stored procedure? I'd like to restrict the possible inputs to 0 or 1, rather than checking for it explicitly after receiving the input.
You can use Booleans as parameters to stored procedures:
procedure p (p_bool in boolean) is...
However you cannot use Booleans in SQL, e.g. select statements:
select my_function(TRUE) from dual; -- NOT allowed
For a number parameter there is no way to declaratively add a "check constraint" to it, you would have to code some validation e.g.
procedure p (p_num in number) is
begin
if p_num not in (0,1) then
raise_application_error(-20001,'p_num out of range');
end if;
...
Yes and no.
You can do..
create or replace package t_bool is
subtype t_bool_num IS PLS_INTEGER RANGE 0..1;
function f_test (i_bool_num t_bool_num) return varchar2;
end t_bool;
/
create or replace package body t_bool is
function f_test (i_bool_num t_bool_num) return varchar2 is
begin
if i_bool_num = 0 then
return 'false';
elsif i_bool_num = 1 then
return 'true';
elsif i_bool_num is null then
return 'null';
else
return to_char(i_bool_num);
end if;
end;
end t_bool;
/
The good news is that, if you do
exec dbms_output.put_line(t_bool.f_test(5));
it reports an error.
The bad news is that if you do
select t_bool.f_test(5) from dual;
then you don't get an error

Resources