Skip to Main Content
  • Questions
  • TO_NUMBER failing in varchar2 column despite using replace and translate

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Srikant.

Asked: October 04, 2016 - 11:17 am UTC

Last updated: October 04, 2016 - 11:49 am UTC

Version: 12c

Viewed 1000+ times

You Asked

Hi Team,

The data in the table is with varchar2 column is :

ID
---
43567
77866
256d5


I have to find out numbers which are greater than 40,000

I used below query but getting invalid number error:

select al.my_num from
(select to_number(id,'99999') my_num from my_num_other where replace(translate(id,'0123456789','0000000000'),'0','') is null) al
where al.my_num>40000





ORA-01722: invalid number
01722. 00000 - "invalid number"
*Cause: The specified number was invalid.
*Action: Specify a valid number.





and Connor said...

This is because we generally merge queries together - you dont get to choose *how* and in what order things are executed, eg


SQL> drop table my_num_other purge;

Table dropped.

SQL>
SQL> create table my_num_other as
  2  select 'X' id from dual
  3  union all
  4  select to_char(rownum+30500) from dual connect by level <= 10000;

Table created.

SQL>
SQL> select al.my_num from
  2  (select to_number(id,'99999') my_num
  3   from   my_num_other
  4   where  replace(translate(id,'0123456789','0000000000'),'0','') is null)  al
  5  where al.my_num>40000;
where al.my_num>40000
      *
ERROR at line 5:
ORA-01722: invalid number


SQL>
SQL> set autotrace traceonly explain
SQL>
SQL> select al.my_num from
  2  (select to_number(id,'99999') my_num
  3   from   my_num_other
  4   where  replace(translate(id,'0123456789','0000000000'),'0','') is null)  al
  5  where al.my_num>40000;

Execution Plan
----------------------------------------------------------
Plan hash value: 1649396075

----------------------------------------------------------------------------------
| Id  | Operation         | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |              |    25 |   150 |     5   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| MY_NUM_OTHER |    25 |   150 |     5   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(TO_NUMBER("ID",'99999')>40000 AND
              REPLACE(TRANSLATE("ID",'0123456789','0000000000'),'0','') IS NULL)


Notice how we just folded everything into one statement, and in this case, the "to_number" came first. You can mitigate this using something like a global temporary table, or a something like below

SQL> with check_this_first as
  2  (select /*+ materialize */ to_number(id,'99999') my_num
  3   from   my_num_other
  4   where  replace(translate(id,'0123456789','0000000000'),'0','') is null
  5  )
  6  select * from check_this_first
  7  where my_num>40000;

    MY_NUM
----------
     40001
     40002
     40003
     40004
     40005
     40006
...
500 rows selected.

SQL>


I wouldnt recommend the above as a "solution", because who knows what changes we'll make in the next version of Oracle and the optimizer might be smarter and shuffle things around. I'm showing it here as a demo of how you would do the global temp table approach, ie, load the data, and *then* do the check.

Alternatively, create your own to_number functin, ie

function my_to_number(p_str varchar2) return number is
  x number;
begin
  x := to_number(p_str);
  return x;
exception
  when others then return null;
end;


Rating

  (3 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Gh, October 04, 2016 - 12:35 pm UTC

select al.my_num from
(select case when replace(translate(id,'0123456789','0000000000'),'0','') is null then to_number(id,'99999') end my_num
from my_num_other
where replace(translate(id,'0123456789','0000000000'),'0','') is null) al
where al.my_num>40000;
where al.my_num>40000

Thanks!!!

A reader, October 04, 2016 - 6:06 pm UTC

It was insane!
Thanks Connor for your help!!!

Another solution

A reader, October 04, 2016 - 7:23 pm UTC

with my_num_other as ( select '43567' id from dual
union all select '77866' from dual
union all select '256d5' from dual
)
select id
from my_num_other
where to_number( nvl2( translate( id, 'x0123456789', 'x' ), null, id ) ) > 40000

More to Explore

Performance

Get all the information about database performance in the Database Performance guide.