Getting invalid identifier error while converting varchar2 column to number datatyope - oracle

I have A table with varchar2 column (That column contains multi values like space, numeric and alphanumeric). And B table with number column.
I need to join A table with B table using above columns.
I have used TO_NUMBER(A.Column) in join condition it is throwing error invalid identifier 'A.Column'. I have tried various way but still i am getting same error.
Example:
In select:
Case when regexp like (A.column,'[0-9]') then to_number(trim(A.column)) else null end as A
In join condition:
Left join B ON
B.column=to_number(A.column)

What version of Oracle?
In 12.2 and later, you can add a default null on conversion error to your to_number call
a
left join b
on b.column = to_number( a.column default null on conversion error )
In earlier versions, you'd need to join on the case statement or on a user-defined function call. Something like
create or replace function safe_to_number( p_str in varchar2 )
return number
is
l_num number;
begin
begin
l_num := to_number( p_str );
exception
when others then
l_num := null;
end;
return l_num;
end;
a
left join b
on b.column = safe_to_number( a.column )
Assuming your case statement works (which implies that all your numbers are integers with no thousands separators)
a
left join b
on b.column = Case when regexp_like (A.column,'[0-9]')
then to_number(trim(A.column))
else null
end

Related

oracle18c function return table

I'm trying to recover a bunch of data in the form of a table
I created the type as follows
create or replace type Structure_type AS OBJECT
(
SWID Integer,
structure_TYPESid Integer,
MAIN_BRANCHid Integer,
Organizational_structureid Integer,
Administrative_structureid Integer,
structure_tree NVarchar2(1000),
structure_NO NVarchar2(500),
Structure_level Integer
)
and create table of type
create or replace type Structure_table_type as table of Structure_type;
Then I created a data retrieval function like the following
create or replace function Get_Who_Runs_Stores
(OoA char) return Structure_table_type is
FunctionResult Structure_table_type;
begin
FunctionResult :=Structure_table_type();
CASE UPPER(OoA)
WHEN 'A' then
FOR x_Structure IN (select b.administrative_tree
from warehouse w join branches_costcenter b on (w.w_branch=b.swid))
LOOP
select
b.swid SWID,
b.costcenter_type_id structure_TYPESid ,
b.main_branch_id MAIN_BRANCHid,
b.direct_branch_cost_center_id Organizational_structureid,
b.direct_adminstration_parent_id Administrative_structureid,
b.administrative_tree structure_tree,
b.administrative_no structure_NO,
b.administrative_level Structure_level
into FunctionResult
from branches_costcenter b
where
x_Structure.Administrative_Tree like '%-' || b.swid || '-%';
end loop;
WHEN 'O' then
FOR x_Structure IN (select b.organizationa_tree
from warehouse w join branches_costcenter b on (w.w_branch=b.swid))
LOOP
select b.swid SWID,b.costcenter_type_id structure_TYPESid ,
b.main_branch_id MAIN_BRANCHid, b.direct_branch_cost_center_id Organizational_structureid,
b.direct_adminstration_parent_id Administrative_structureid,b.organizationa_tree structure_tree,
b.organizationa_no structure_NO ,b.organizationa_level Structure_level into FunctionResult
from branches_costcenter b
where
x_Structure.Organizationa_Tree like '%-' || b.swid || '-%';
end loop;
else
return null;
end case ;
return(FunctionResult);
end Get_Who_Runs_Stores;
Error: PL/SQL: ORA-00947: not enough values
Line: 45
Text: from branches_costcenter b
I tried the SQL sentence and it works correctly
select
b.swid SWID,
b.costcenter_type_id structure_TYPESid ,
b.main_branch_id MAIN_BRANCHid,
b.direct_branch_cost_center_id Organizational_structureid,
b.direct_adminstration_parent_id Administrative_structureid,
b.administrative_tree structure_tree,
b.administrative_no structure_NO,
b.administrative_level Structure_level
from branches_costcenter b
where
'-1-2-3-12-24-' like '%-' || b.swid || '-%';
How can I get the required data and what is the best way to do so?
Do I have to define all that I want to retrieve data from the function in the form of a table?
Sorry, my English is bad
Your function will be horrible slow. Apart from that the issue is following: If you run
FOR x_Structure IN (select ...) LOOP
SELECT ...
INTO FunctionResult
FROM ...;
END LOOP;
RETURN FunctionResult;
then you insert row by row into FunctionResult. At the end you return only the last row from your select, all other rows have been overwritten.
Try
SELECT ...
BULK COLLECT INTO FunctionResult
FROM ...;
You could also insert row by row, but this is much slower:
FOR x_Structure IN (select ...) LOOP
FunctionResult.Extend();
SELECT ...
INTO FunctionResult(FunctionResult.LAST)
FROM ...;
END LOOP;
You cannot select into FunctionResult directly. The statement must be like this:
SELECT Structure_type(b.swid, b.costcenter_type_id, ..., b.administrative_level)
BULK COLLECT INTO FunctionResult
FROM ...
Update
Depending on your client it might be easier and better to return a RefCursor. Would be like this:
create or replace function Get_Who_Runs_Stores(OoA char) return SYS_REFCURSOR is
res SYS_REFCURSOR;
begin
OPEN res FOR
SELECT b.swid SWID,b.costcenter_type_id structure_TYPESid, ...
FROM ...
WHERE ...;
RETURN res;
end;
You don't have to create any TABLE or OBJECT type, just return the RefCursor.

How can I speed up the oracle query? ( where in( bla bla ))

I have 2000 numbers (Uniq Primary Key). I want to get contact information of numbers. My database is Oracle.
I use IN(bla,bla) in my query. It works slow because of this.
Example My Query:
SELECT p.*,t.*
FROM PERSONEL p
LEFT OUTER JOIN CODE_TITLE t ON t.Id = p.TitleId
WHERE ID IN(1,2,....,2000)
When the query runs, it takes about 10-12 seconds.
Is there a method to use instead of IN(bla, bla)? Can you explain with an example ?
Put your numbers (or whatever they really are) in a table. Let's call it LIST_TABLE. Then
SELECT p.*,t.*
FROM PERSONEL p
LEFT OUTER JOIN CODE_TITLE t ON t.Id = p.TitleId
WHERE ID IN(select list_id from list_table)
The type of table for LIST_TABLE (normal, GTT, external) will depend on where the values come from and your best mechanism for loading them.
You can use xmltable('1 to 2000') as a derived table in order to generate integer set starting form 1 upto 2000, incrementing by 1 :
SELECT p.*,t.*
FROM PERSONEL p
LEFT JOIN CODE_TITLE t ON t.Id = p.TitleId
WHERE Id IN ( SELECT TO_NUMBER(column_value) FROM xmltable('1 to 2000') )
P.S. indeed using WHERE ID BETWEEN 1 AND 2000 would suffice with index created on CODE_TITLE.ID in order to increase the query performance.
create or replace type myTcType as table of number(16,0);
create or replace function in_list(p_string in varchar2) return myTcType
as
l_data myTcType := myTcType();
l_string long default p_string || ',';
l_n number;
begin
loop
exit when l_string is null;
l_data.extend;
l_n := instr(l_string, ',');
l_data(l_data.count) := substr(l_string,1,l_n-1);
l_string := substr(l_string,l_n+1);
end loop;
return l_data;
end;
select * from table(cast(in_list('1,2,3,4,5') as myTcType));

PL/SQL : Need to compare data for every field in a table in plsql

I need to create a procedure which will take collection as an input and compare the data with staging table data row by row for every field (approx 50 columns).
Business logic :
whenever a staging table column value will mismatch with the corresponding collection variable value then i need to update 'FAIL' into staging table STATUS column and reason into REASON column for that row.
If matched then need to update 'SUCCESS' in STATUS column.
Payload will be approx 500 rows in each call.
I have created below sample script:
PKG Specification :
CREATE OR REPLACE
PACKAGE process_data
IS
TYPE pass_data_rec
IS
record
(
p_eid employee.eid%type,
p_ename employee.ename%type,
p_salary employee.salary%type,
p_dept employee.dept%type
);
type p_data_tab IS TABLE OF pass_data_rec INDEX BY binary_integer;
PROCEDURE comp_data(inpt_data IN p_data_tab);
END;
PKG Body:
CREATE OR REPLACE
PACKAGE body process_data
IS
PROCEDURE comp_data (inpt_data IN p_data_tab)
IS
status VARCHAR2(10);
reason VARCHAR2(1000);
cnt1 NUMBER;
v_eid employee_copy.eid%type;
v_ename employee_copy.ename%type;
BEGIN
FOR i IN 1..inpt_data.count
LOOP
SELECT ec1.eid,ec1.ename,COUNT(*) over () INTO v_eid,v_ename,cnt1
FROM employee_copy ec1
WHERE ec1.eid = inpt_data(i).p_eid;
IF cnt1 > 0 THEN
IF (v_eid=inpt_data(i).p_eid AND v_ename = inpt_data(i).p_ename) THEN
UPDATE employee_copy SET status = 'SUCCESS' WHERE eid = inpt_data(i).p_eid;
ELSE
UPDATE employee_copy SET status = 'FAIL' WHERE eid = inpt_data(i).p_eid;
END IF;
ELSE
NULL;
END IF;
END LOOP;
COMMIT;
status :='success';
EXCEPTION
WHEN OTHERS THEN
status:= 'fail';
--reason:=sqlerrm;
END;
END;
But in this approach i have below mentioned issues.
Need to declare all local variables for each column value.
Need to compare all variable data using 'and' operator. Not sure whether it is correct way or not because if there are 50 columns then if condition will become very heavy.
IF (v_eid=inpt_data(i).p_eid AND v_ename = inpt_data(i).p_ename) THEN
Need to update REASON column when any column data mismatched (first mismatched column name) for that row, in this approach i am not able to achieve.
Please suggest any other good way to achieve this requirement.
Edit :
There is only one table at my end i.e target table. Input will come from any other source as collection object.
REVISED Answer
You could load the the records into t temp table, but unless you want additional processing it's not necessary. AFAIK there is no way to identify the offending column (first one only) without slugging through column-by-column. However, your other concern having to declare a variable is not necessary. You can declare a single variable defined as %rowtype which gives you access to each column by name.
Looping through an array of data to find the occasional error is just bad (imho) with SQL available to eliminate the good ones in one fell swoop. And it's available here. Even though your input is a array we can use as a table by using the TABLE operator, which allows an array (collection) as though it were a database table. So the MINUS operator can till be employed. The following routine will set the appropriate status and identify the first miss matched column for each entry in the input array. It reverts to your original definition in package spec, but replaces the comp_data procedure.
create or replace package body process_data
is
procedure comp_data (inpt_data in p_data_tab)
is
-- define local array to hold status and reason for ecah.
type status_reason_r is record
( eid employee_copy.eid%type
, status employee_copy.status%type
, reason employee_copy.reason%type
);
type status_reason_t is
table of status_reason_r
index by pls_integer;
status_reason status_reason_t := status_reason_t();
-- define error array to contain the eid for each that have a mismatched column
type error_eids_t is table of employee_copy.eid%type ;
error_eids error_eids_t;
current_matched_indx pls_integer;
/*
Helper function to identify 1st mismatched column in error row.
Here is where we slug our way through each column to find the first column
value mismatch. Note: There is actually validate the column sequence, but
for purpose here we'll proceed in the input data type definition.
*/
function identify_mismatch_column(matched_indx_in pls_integer)
return varchar2
is
employee_copy_row employee_copy%rowtype;
mismatched_column employee_copy.reason%type;
begin
select *
into employee_copy_row
from employee_copy
where employee_copy.eid = inpt_data(matched_indx_in).p_eid;
-- now begins the task of finding the mismatched column.
if employee_copy_row.ename != inpt_data(matched_indx_in).p_ename
then
mismatched_column := 'employee_copy.ename';
elsif employee_copy_row.salary != inpt_data(matched_indx_in).p_salary
then
mismatched_column := 'employee_copy.salary';
elsif employee_copy_row.dept != inpt_data(matched_indx_in).p_dept
then
mismatched_column := 'employee_copy.dept';
-- elsif continue until ALL columns tested
end if;
return mismatched_column;
exception
-- NO_DATA_FOUND is the one error that cannot actually be reported in the customer_copy table.
-- It occurs when an eid exista in the input data but does not exist in customer_copy.
when NO_DATA_FOUND
then
dbms_output.put_line( 'Employee (eid)='
|| inpt_data(matched_indx_in).p_eid
|| ' does not exist in employee_copy table.'
);
return 'employee_copy.eid ID is NOT in table';
end identify_mismatch_column;
/*
Helper function to find specified eid in the initial inpt_data array
Since the resulting array of mismatching eid derive from a select without sort
there is no guarantee the index values actually match. Nor can we sort to build
the error array, as there is no way to know the order of eid in the initial array.
The following helper identifies the index value in the input array for the specified
eid in error.
*/
function match_indx(eid_in employee_copy.eid%type)
return pls_integer
is
l_at pls_integer := 1;
l_searching boolean := true;
begin
while l_at <= inpt_data.count
loop
exit when eid_in = inpt_data(l_at).p_eid;
l_at := l_at + 1;
end loop;
if l_at > inpt_data.count
then
raise_application_error( -20199, 'Internal error: Find index for ' || eid_in ||' not found');
end if;
return l_at;
end match_indx;
-- Main
begin
-- initialize status table for each input enter
-- additionally this results is a status_reason table in a 1:1 with the input array.
for i in 1..inpt_data.count
loop
status_reason(i).eid := inpt_data(i).p_eid;
status_reason(i).status :='SUCCESS';
end loop;
/*
We can assume the majority of data in the input array is valid meaning the columns match.
We'll eliminate all value rows by selecting each and then MINUSing those that do match on
each column. To accomplish this cast the input with TABLE function allowing it's use in SQL.
Following produces an array of eids that have at least 1 column mismatch.
*/
select p_eid
bulk collect into error_eids
from (select p_eid, p_ename, p_salary, p_dept from TABLE(inpt_data)
minus
select eid, ename, salary, dept from employee_copy
) exs;
/*
The error_eids array now contains the eid for each miss matched data item.
Mark the status as failed, then begin the long hard process of identifying
the first column causing the mismatch.
The following loop used the nested functions to slug the way through.
This keeps the main line logic clear.
*/
for i in 1 .. error_eids.count -- if all inpt_data rows match then count is 0, we bypass the enttire loop
loop
current_matched_indx := match_indx(error_eids(i));
status_reason(current_matched_indx).status := 'FAIL';
status_reason(current_matched_indx).reason := identify_mismatch_column(current_matched_indx);
end loop;
-- update employee_copy with appropriate status for each row in the input data.
-- Except for any cid that is in the error eid table but doesn't exist in the customer_copy table.
forall i in inpt_data.first .. inpt_data.last
update employee_copy
set status = status_reason(i).status
, reason = status_reason(i).reason
where eid = inpt_data(i).p_eid;
end comp_data;
end process_data;
There are a couple other techniques used you may want to look into if you are not familiar with them:
Nested Functions. There are 2 functions defined and used in the procedure.
Bulk Processing. That is Bulk Collect and Forall.
Good Luck.
ORIGINAL Answer
It is NOT necessary to compare each column nor build a string by concatenating. As you indicated comparing 50 columns becomes pretty heavy. So let the DBMS do most of the lifting. Using the MINUS operator does exactly what you need.
... the MINUS operator, which returns only unique rows returned by the
first query but not by the second.
Using that this task needs only 2 Updates: 1 to mark "fail", and 1 to mark "success". So try:
create table e( e_id integer
, col1 varchar2(20)
, col2 varchar2(20)
);
create table stage ( e_id integer
, col1 varchar2(20)
, col2 varchar2(20)
, status varchar2(20)
, reason varchar2(20)
);
-- create package spec and body
create or replace package process_data
is
procedure comp_data;
end process_data;
create or replace package body process_data
is
package body process_data
procedure comp_data
is
begin
update stage
set status='failed'
, reason='No matching e row'
where e_id in ( select e_id
from (select e_id, col1, col2 from stage
except
select e_id, col1, col2 from e
) exs
);
update stage
set status='success'
where status is null;
end comp_data;
end process_data;
-- test
-- populate tables
insert into e(e_id, col1, col2)
select (1,'ABC','def') from dual union all
select (2,'No','Not any') from dual union all
select (3,'ok', 'best ever') from dual union all
select (4,'xx','zzzzzz') from dual;
insert into stage(e_id, col1, col2)
select (1,'ABC','def') from dual union all
select (2,'No','Not any more') from dual union all
select (4,'yy', 'zzzzzz') from dual union all
select (5,'no e','nnnnn') from dual;
-- run procedure
begin
process_data.comp_date;
end;
-- check results
select * from stage;
Don't ask. Yes, you to must list every column you wish compared in each of the queries involved in the MINUS operation.
I know the documentation link is old (10gR2), but actually finding Oracle documentation is a royal pain. But the MINUS operator still functions the same in 19c;

PL/SQL - How to concatenate values from different queries into one resultset?

Image I have the following queries in a package:
SELECT col1 FROM table WHERE id=5;
SELECT otherCol FROM otherTable WHERE id=78;
I want to return one record with two columns containing the values 'col1' and 'otherCol'.
In my case, I'd have lots of subqueries and DECODE statements so for readability I want to split it up into different queries something ala:
var result = {}; -- Unfortunately PL/SQL doesn't cope very well with JavaScript.
SELECT col1 INTO someVar FROM table WHERE id=5;
IF someVar = 1 THEN
result.X = SomeQuery();
ELSE
result.X = SomeEntirelyDifferentQuery();
END IF;
SELECT col INTO result.Z FROM tab1;
SELECT coz INTO result.Q FROM tab2;
IF result.Z IS NULL THEN
result.B = Query1();
ELSE
result.B = Query2();
END IF;
... and so on.
RETURN result;
So basically I want to create an "empty record" and then based on some conditions assign different values to columns in that record
This could be solved with DECODE but the resulting query is both not very readable nor very performant.
You have to define an object type the function can return.
CREATE OR REPLACE TYPE fancy_function_result_row as object
(
X number,
Y number,
Q date,
Z varchar2(30)
);
create or replace function fancy_function(...)
return fancy_function_result_row
as
my_record fancy_function_result_row := fancy_function_result_row();
begin
my_record.X := 1;
etc...
end;
If you want to insert the record into a table, it might be a lot easier simply defining a rowtype of that table.
declare
row_my_table my_table%rowtype;
begin
row_my_table.X := etc..
insert into my_table values row_my_table;
end;
--To concat the values 'col1' and 'otherCol':
with r1 as (select col1 from table where id=5),
r2 as (select otherCol from otherTable WHERE id=78),
select r1.col1 || ' concat with ' || r2.othercol from r1, r2
--To do this condition using DECODE:
SELECT DECODE (col1,
1,
(SELECT 1 FROM query1),
(SELECT 1 FROM DifferentQuery)
)
FROM TABLE

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