oracle dynamic validation - oracle

Consider a table with 3 columns
Value1   operator     value2
abc          =               xyz
1            !=               2
5            >                8
9            <=              11
xyz          is not Null
{null val}   is null
I want to write a genric function which returns the validated result either true or false
by validating value1 with value2 using the operator
or check value1 in case of 'is null' and 'is not null' operators (Last two cases)

You could do something like this with dynamic SQL (note that in reality you'd need to add a bunch of logic to prevent SQL injection attacks)
SQL> ed
Wrote file afiedt.buf
1 create or replace function evaluate( p_val1 in varchar2,
2 p_op in varchar2,
3 p_val2 in varchar2 )
4 return varchar2
5 is
6 l_result varchar2(1);
7 l_sql_stmt varchar2(1000);
8 begin
9 if( p_val2 is not null )
10 then
11 l_sql_stmt := 'select (case when :1 ' || p_op || ' :2 then ''Y'' else ''N'' end) from dual';
12 execute immediate l_sql_stmt
13 into l_result
14 using p_val1, p_val2;
15 else
16 l_sql_stmt := 'select (case when :1 ' || p_op || ' then ''Y'' else ''N'' end) from dual';
17 execute immediate l_sql_stmt
18 into l_result
19 using p_val1;
20 end if;
21 return l_result;
22* end;
SQL> /
Function created.
SQL> select evaluate( 'xyz', 'is not null', null )
2 from dual;
EVALUATE('XYZ','ISNOTNULL',NULL)
--------------------------------------------------------------------------------
Y
SQL> select evaluate( 'abc', '=', 'xyz' )
2 from dual;
EVALUATE('ABC','=','XYZ')
--------------------------------------------------------------------------------
N
Since you are storing the data in a table, that implies that each column is a VARCHAR2. My guess, though, is that you don't always want to use string comparison semantics. For example, the string '9' is greater than the string '11' while the number 9 is less than the number 11. If you want to use something other than string comparison semantics, you'd need to add code that inspected the parameters and applied whatever logic you would like to determine what comparison semantics you want to apply and then generated the appropriate dynamic SQL statement.
I would strongly question the wisdom of the requirement, however. First off, it makes little sense from a data model standpoint to store numeric data in a VARCHAR2 column-- when you find yourself trying to mix string and numeric data in the same column, you've almost always made a data model mistake. I would also be hard-pressed to imagine what business problem you are trying to solve that would involve this sort of dynamic function-- it seems likely that there is a better way to solve whatever problem you are attempting to solve.

It seems to me you can escape dynamic SQL with unwinding logic in a list of rules.
If you need to check one record use something like
with CheckedTable as (select * from t where primary_key = ID)
select count(*) from
(
select * from CheckedTable where operator = '=' and value1 = value2
union all
select * from CheckedTable where operator = '!=' and value1 <> value2
union all
select * from CheckedTable where operator = '>' and value1 > value2
union all
...
select * from CheckedTable where operator = 'is null' and value1 is null
)
So 1 = TRUE and 0 = FALSE.
P.S. If you want to support IN clause you can use this workaround (remove brackets from value2!)
select * from CheckedTable where operator = 'in' and ',' || value2 || ',' like '%,' || value1 || ',%'
proof on SQLFiddle

looking through above question and the results, i came up with some points that we should take care of using string and comparisons:
----->
its possible to compare numeric values with any comparison function ,ie;
=, !=, <, >, <=, >=, IS NULL, LIKE, BETWEEN, IN
but beware of using quotes;
SELECT (CASE WHEN '9' > '12' THEN 'Y' ELSE 'N' END) FROM dual;-- results Y
because, here the implicit positional comparison is done by checking 9>1 (1 in the 12), so the condition 9>1 is true, so results as Y.
The correct way of comparison with numeric is without quotes:
SELECT (CASE WHEN 9 > 12 THEN 'Y' ELSE 'N' END) FROM dual;--results N
-----> its meaningless to compare a string with another with '>' and '<'
-----> we've UTL_MATCH package especially for string matching.
SELECT utl_match.edit_distance( 'abc','abcde' ) FROM dual;--results 2, shows no:of edits required to make string1 to string2.
SELECT UTL_MATCH.EDIT_DISTANCE_similarity('abc', 'abcde') FROM DUAL;-- results 60, shows the similarity percentage of two strings.

Related

split a string into individual elements in PL/SQL

This is for PL/SQL
I am being passed in a series of strings, delimited by a comma, in a string.
I would like to take those delimited strings and create an IN clause within my query.
I was thinking a simply add an open parenthesis and single quote to the front and, at the end of the string and single quote with a closing parenthesis, trim the spaces, and swap the commas out with a quote comma quote string. the problem falls when the string has an embedded space in the delimited string.
For example, the string that is being passed in is
TNR, abc, D N, w
What i am looking to receive after processing it would be
('TNR','ABC','D N','W')
I'm not sure where i should begin.
Use REPLACE to change , into ', ' and then prepend (' and append '):
DECLARE
list VARCHAR2(50) := 'TNR, abc, D N, w';
BEGIN
list := '(''' || REPLACE(list, ', ', ''', ''') || ''')';
DBMS_OUTPUT.PUT_LINE(list);
END;
/
If the white space after the comma is optional then you can use REGEXP_REPLACE rather than REPLACE:
DECLARE
list VARCHAR2(50) := 'TNR,abc, D N, w';
BEGIN
list := '(''' || REGEXP_REPLACE(list, ',\s*', ''', ''') || ''')';
DBMS_OUTPUT.PUT_LINE(list);
END;
/
Which both output:
('TNR', 'abc', 'D N', 'w')
db<>fiddle here
What you described is way too complicated. Just split the string into rows and use them in an IN clause as a subquery.
This is example that runs in SQL*Plus (hence single quotes and && for the substitution variable); query returns one row (from the dual table) as its dummy column contains letter X which is part of the input string:
SQL> SELECT *
2 FROM DUAL
3 WHERE dummy IN ( SELECT TRIM (REGEXP_SUBSTR ('&&par_string',
4 '[^,]+',
5 1,
6 LEVEL))
7 FROM DUAL
8 CONNECT BY LEVEL <= REGEXP_COUNT ('&&par_string', ',') + 1);
Enter value for par_string: TNR, abc, D N,X, w
D
-
X
SQL>
Subquery itself returns
SQL> SELECT TRIM (REGEXP_SUBSTR ('&&par_string',
2 '[^,]+',
3 1,
4 LEVEL))
5 FROM DUAL
6 CONNECT BY LEVEL <= REGEXP_COUNT ('&&par_string', ',') + 1;
Enter value for par_string: TNR, abc, D N,X, w
TRIM(REGEXP_SUBSTR
------------------
TNR
abc
D N
X
w
SQL>
If you use bind variable, you'd use e.g. :par_string instead of '&&par_string'; or, if it was passed to a procedure or a function, you'd just just the parameter name - par_string.

Getting PLS-00103 Error. PLS-00103: Encountered the symbol "RETURN"

I am new to PL/sql and I am currently writing some PL/SQL code that has to extract data from two tables. The code that I have so far keeps getting error PLS-00103: Encountered the symbol "RETURN" when expecting one of
the following:
* & = - + ; < / > at in is mod remainder not rem
This is my code at the moment
CREATE OR REPLACE FUNCTION LISTNATION(N_NAME in VARCHAR2, R_NAME IN VARCHAR2, R_REGIONKEY IN NUMBER)
2 RETURN VARCHAR2 IS
3 R_REGIONKEY NUMBER(3);
4 R_NAME VARCHAR2(50);
5 N_NAME VARCHAR2(50);
6
7 BEGIN
8 select r_regionkey, r_name, n_name
9 from region
10 inner join nation
11 on r_regionkey = n_regionkey;
12
13 dbms_output.put_line = (R_REGIONKEY || ' ' || R_NAME || ':' || N_NAME || ',')
14
15 END;
16 /
The immediate cause of the error you're getting(in the original version of the question) is that you're missing missing a semicolon at the end of line 4.
But there are quite a few other issues:
you are using double-quotes " around string literals instead of single quotes '. Those are for identifiers, not strings.
your local variables are the same as the arguments, but you don't need all those arguments anyway.
you aren't selecting into anything; and your query will return multiple rows.
your query doesn't have a where clause so it will look for data in all regions.
you've mangled the dbms_output call; it's missing a closing parenthesis (again, in the original question) and should not have the =.
you're returning test but haven't declared that.
So, to have the function use dbms_output to display the results - which relies on the caller handling that output, which you shouldn't assume - you could maybe do:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_R_NAME REGION.R_NAME%TYPE;
L_N_NAME NATION.N_NAME%TYPE;
BEGIN
FOR l_row IN (
select r.r_name, n.n_name
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey
) LOOP
dbms_output.put_line (P_R_REGIONKEY || ' ' || l_row.R_NAME || ':' || l_row.N_NAME);
END LOOP;
RETURN 'test';
END;
/
That adds a filter to your query, turns it into an implicit cursor, and loops over the results.
db<>fiddle with some made-up data, with table and column names taken from your attempt.
It isn't clear what you actually want to return; you might want a comma-separated list of nation names, in which case look at the listagg() function. For example, you could do something like:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_RESULT VARCHAR2(4000);
BEGIN
select listagg(n.n_name, ',') within group (order by n.n_name)
into l_result
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey;
RETURN l_result;
END;
/
and you could then call that to get back a single list of values.
db<>fiddle
Although using a PL/SQL function wrapper around that query doesn't seem very useful. Presumably this is an exercise though...
is there anyway for this to display it such that it only shows the region key and region once while listing all nations?
You can change the second function to include the region info, concatenating that with the listagg result:
CREATE OR REPLACE FUNCTION LISTNATION(P_R_REGIONKEY IN NUMBER)
RETURN VARCHAR2 IS
L_RESULT VARCHAR2(4000);
BEGIN
select r.r_regionkey || ' ' || r.r_name || ': '
|| listagg(n.n_name, ',') within group (order by n.n_name)
into l_result
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
where r.r_regionkey = p_r_regionkey
group by r.r_regionkey, r.r_name;
RETURN l_result;
END;
/
then call that for each region key you're interested in.
db<>fiddle
The way you've phrased it makes it sound like you don't want to pass in a region key and instead want to see all regions at once; and want to use dbms_output, which isn't ideal; and don't really want to return anything. So you could use a procedure instead, change the cursor query to bring back the region name, and then concatenate in the put_line call inside the loop:
CREATE OR REPLACE PROCEDURE LISTNATION IS
BEGIN
FOR l_row IN (
select r.r_regionkey, r.r_name,
listagg(n.n_name, ',') within group (order by n.n_name) as names
from region r
inner join nation n
on r.r_regionkey = n.n_regionkey
group by r.r_regionkey, r.r_name
order by r.r_regionkey
) LOOP
dbms_output.put_line (l_row.R_REGIONKEY || ' ' || l_row.R_NAME || ': ' || l_row.names);
END LOOP;
END;
/
dbms_output:
1 EMEA: FRANCE,UNITED KINGDOM
2 APAC: CHINA
db<>fiddle
There are lots of variations of course, it depends exactly what you want to have happen, but you should be able to adapt one of these approaches. Another is to have a function or procedure generate a ref cursor, but again it's not clear what you want. But using dbms_output isn't a great idea, as the caller may not be using a client that looks for and displays that.

How we can get the value from string: 1;BWQTY;4|2;NGRID;34664 and store into a variable if any specific SUBSTRING found?

I have a table name as event_creation_template which having column as TEXT ,which contains value like below :
1;BWQTY;4|2;NGRID;34664
1;DTYPE;Amount|2;LOYAL;R
How we can get the value from string:
1;BWQTY;4|2;NGRID;34664
If string is having BWQTY then store 4 into variable again search for the NGRID ,if string contains NGRID , we need in 34664 into different variable.
using Oracle PL/SQL.
considering the string like 'BWQTY' will always lie between two non-aphanumeric characters and the corresponding values like 4 lies between either non-alphanumeric character or appears at the end like in the two examples above you can use below code
CREATE OR REPLACE PR_PREP
IS
V_VAR1 VARCHAR2(100);
V_-VAR2 VARCHAR2(100);
V_SEARCH_VAL1 VARCHAR2(100) := 'BWQTY';
V_SEARCH_VAL2 VARCHAR2(100) := 'NGRID';
CURSOR C1
IS
SELECT TEXT FROM EVENT_CREATION_TEMPLATE;
BEGIN
FOR REC IN C1
LOOP
IF INSTR(REC.TEXT,V_SEARCH_VAL1) > 0 THEN
SELECT NVL(SUBSTR(SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1), 1, REGEXP_INSTR( SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1), '[^0-9a-zA-Z]',1)-1), SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1))
INTO V_VAR1
FROM DUAL ;
END IF;
IF INSTR(REC.TEXT,V_SEARCH_VAL2) > 0 THEN
SELECT NVL(SUBSTR(SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1), 1, REGEXP_INSTR( SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1), '[^0-9a-zA-Z]',1)-1), SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1))
INTO V_VAR2
FROM DUAL ;
END IF;
-- DO FURTHER WORK
END LOOP;
END;
You can use regular expressions
for BWQTY:
select str,
SUBSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'),
INSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'), ';') + 1 ,
LENGTH(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)')) - INSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'), ';')) AS VALUE
from test
for NGRID:
select str,
SUBSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'),
INSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'), ';') + 1 ,
LENGTH(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)')) - INSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'), ';')) AS VALUE
from test
Where str is your input string...

WHERE condition for "starts with any of the values in a CSV list" [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
how to convert csv to table in oracle
I have a query in PL/SQL that is built to handle input in a variable as a "starts-with" filter:
WHERE product_group LIKE strProductGroup || '%'
As a new "feature", the input variable could contain comma separated values. So, where before I would expect something like "ART", I could now see "ART,DRM".
I'd like to avoid building this query as a string and using EXECUTE IMMEDIATE, if possible. Can anyone think of a way to write a WHERE condition that is the equivalent of saying "starts with any of the values in a CSV list" in Oracle 10g?
Assuming that you don't have any restrictions on creating a couple of additional objects (a collection type and a function), you can parse the list into a collection that you can reference in your query. Tom Kyte has a good discussion on this in his variable "IN" list thread.
If you use Tom's myTableType and in_list function, for example
SQL> create or replace type myTableType as table
of varchar2 (255);
2 /
Type created.
ops$tkyte#dev8i> create or replace
function in_list( p_string in varchar2 ) return myTableType
2 as
3 l_string long default p_string || ',';
4 l_data myTableType := myTableType();
5 n number;
6 begin
7 loop
8 exit when l_string is null;
9 n := instr( l_string, ',' );
10 l_data.extend;
11 l_data(l_data.count) :=
ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
12 l_string := substr( l_string, n+1 );
13 end loop;
14
15 return l_data;
16 end;
17 /
Then you can search for equality relatively easily.
WHERE product_group IN (SELECT column_value
FROM TABLE( in_list( strProductGroup )))
But you want to do a LIKE which is a bit more challenging since you can't do a LIKE on an in-list. You could, however, do something like
select *
from emp e,
(select '^' || column_value search_regexp
from table( in_list( 'KIN,BOB' ))) a
where regexp_like( e.ename, a.search_regexp )
This will search the EMP table for any employees where the ENAME begins with either KIN or BOB. In the default SCOTT.EMP table, this will return just one row, the row where the ENAME is "KING"
I found another post that gave me an idea. In my specific case, the values in the input will all be 3 characters, so I can do the following:
AND SUBSTR(product_group, 0, 3) IN
(SELECT regexp_substr(strProductGroup, '[^,]+', 1, LEVEL)
FROM dual
CONNECT BY LEVEL <= length(regexp_replace(strProductGroup, '[^,]+')) + 1)
I like this solution, because it does not require additional types or functions, but is pretty limited to my specific case.

check if "it's a number" function in 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.

Resources