71
SELECT TO_NUMBER('*') FROM DUAL

This obviously gives me an exception:

ORA-01722: invalid number

Is there a way to "skip" it and get 0 or NULL instead?

The whole issue: I have NVARCHAR2 field, which contains numbers and not almost ;-) (like *) and I need to select the biggest number from the column.

Yes, I know it is a terrible design, but this is what I need now... :-S

UPD:

For myself I've solved this issue with

COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+')), 0)
Lukasz Szozda
  • 162,964
  • 23
  • 234
  • 275
zerkms
  • 249,484
  • 69
  • 436
  • 539

9 Answers9

55

From Oracle Database 12c Release 2 you could use TO_NUMBER with DEFAULT ... ON CONVERSION ERROR:

SELECT TO_NUMBER('*' DEFAULT 0 ON CONVERSION ERROR) AS "Value"
FROM DUAL;

Or CAST:

SELECT CAST('*' AS NUMBER DEFAULT 0 ON CONVERSION ERROR) AS "Value"
FROM DUAL;

db<>fiddle demo

Lukasz Szozda
  • 162,964
  • 23
  • 234
  • 275
32
COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+(\.\d+)?')), 0) 

will also get numbers with scale > 0 (digits to the right of the decimal point).

pweitzman
  • 561
  • 5
  • 3
  • 3
    This is a good solution, but the regex should be adjusted for each specific scenario. This regex might work better: `^\-?\d*\.?\d*$` which allows negative numbers and numbers starting with `.`, and disallows non-numeric trailing characters. The `^\d+(\.\d+)?` regex yields `1` if `field` contains `1x`, and `0' if `field` contains `0.0.1`, and `0` if `field` contains `0 6`, etc. – hmqcnoesy Aug 25 '16 at 16:33
16

I couldn't find anything better than this:

function safe_to_number(p varchar2) return number is
    v number;
  begin
    v := to_number(p);
    return v;
  exception when others then return 0;
end;
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • Yep, I also decided to write my own function, a bit different than yours (because `54-3` should become `54`, i did not mention that, sorry). – zerkms Dec 20 '10 at 04:58
  • 11
    You'd better ignore only ORA-01722 here. – milan Dec 20 '10 at 12:51
  • 5
    Take into account that [custom pl/sql functions have significant performace overhead](https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:60122715103602) that may not be suitable for heavy queries. – Vadzim Feb 06 '15 at 12:34
  • 2
    Note the answer below showing how to handle this natively from 12c r2 onwards https://stackoverflow.com/a/45886745/587365 – Andrew Spencer Apr 05 '19 at 14:59
  • 1
    i agree with milan about ignoring only ORA-01722 and would write `exception when invalid_number then return 0;` instead – stackunderflow Nov 11 '20 at 09:43
12
select COALESCE(TO_NUMBER(REGEXP_SUBSTR( field, '^(-|+)?\d+(\.|,)?(\d+)?$')), 0) from dual;

It will convert 123 to 123, but 123a or 12a3 to 0.

sOliver
  • 321
  • 3
  • 6
7

Fitting the original question and rather old skool

select a, decode(trim(translate(b,'0123456789.',' ')),null,to_number(b),0)  from 
(
    select '1' a, 'not a number' b from dual
    union
    select '2' a, '1234' b from dual
)
stjohnroe
  • 3,168
  • 1
  • 27
  • 27
  • @Ronnis, the builtins often are, but as you say, they perform well. Works from around Oracle7 (from memory) – stjohnroe Dec 20 '10 at 22:53
  • this will make `543` from the string `54-3`. I did not mention it in the question, but in this case I expect `54` (and that is what my solution with regexp does). – zerkms Dec 20 '10 at 23:24
  • @zerkins,actually thought it would produce 0 from 54-3, anyway, as I said was answering the original question before comments. Like your final solution though. – stjohnroe Dec 20 '10 at 23:30
  • Fails if number contains spaces: select a, decode(trim(translate(b,'0123456789.',' ')),null,to_number(b),0) from ( select '1' a, '12 34' b from dual ) – Ilya Kharlamov Nov 27 '14 at 20:30
  • Couldn't use regex in my application so this worked great – EvilEddie Oct 16 '17 at 21:30
1
select DECODE(trim(TRANSLATE(replace(replace(A, ' '), ',', '.'), '0123456789.-', ' ')),
              null,
              DECODE(INSTR(replace(replace(A, ' '), ',', '.'), '.', INSTR(replace(replace(A, ' '), ',', '.'), '.') + 1),
                     0,
                     DECODE(INSTR(replace(replace(A, ' '), ',', '.'), '-', 2),
                            0,
                            TO_NUMBER(replace(replace(A, ' '), ',', '.'))))) A
  from (select '-1.1' A from DUAL union all select '-1-1' A from DUAL union all select ',1' A from DUAL union all select '1..1' A from DUAL) A;

This code excludes such strings as: -1-1, 1..1, 12-2 and so on. And I haven't used regular expressions here.

usbo
  • 131
  • 1
  • 7
1

It's probably a bit messy rolling your own regexp to test for a number, but the code below might work. I think the other solution by Gabe involving a user defined function is more robust since you are using the built in Oracle functionality (and my regexp is probably not 100% correct) but it might be worth a go:

with my_sample_data as (
  select '12345' as mynum from dual union all
  select '54-3' as mynum from dual union all
  select '123.4567' as mynum from dual union all
  select '.34567' as mynum from dual union all
  select '-0.3462' as mynum from dual union all
  select '0.34.62' as mynum from dual union all
  select '1243.64' as mynum from dual 
)
select 
  mynum, 
  case when regexp_like(mynum, '^-?\d+(\.\d+)?$') 
    then to_number(mynum) end as is_num
from my_sample_data

This will then give the following output:

MYNUM   IS_NUM
-------- ----------
12345   12345
54-3    
123.4567    123.4567
.34567  
-0.3462 -0.3462
0.34.62 
1243.64 1243.64
Mike Meyers
  • 2,885
  • 1
  • 20
  • 26
0

A combination of previous solutions (from @sOliver and @Mike Meyers) and trying to grab as much numbers as possible by removing the last '$' from REGEXP.

It can be used to filter the actual number from a configuration table, and have a "kind-of" comment next to the number as '12 Days'.

with my_sample_data as ( select '12345' as mynum from dual union all select '123.4567' as mynum from dual union all select '-0.3462' as mynum from dual union all select '.34567' as mynum from dual union all select '-.1234' as mynum from dual union all select '**' as mynum from dual union all select '0.34.62' as mynum from dual union all select '24Days' as mynum from dual union all select '42ab' as mynum from dual union all select '54-3' as mynum from dual ) SELECT mynum, COALESCE( TO_NUMBER( REGEXP_SUBSTR( mynum, '^(-|+)?\d*(.|,)?(\d+)?') ) , 0) is_num FROM my_sample_data;

would give


MYNUM    IS_NUM                                  
-------- ----------
12345    12345                                   
123.4567 123.4567                                
-0.3462  -0.3462                                 
.34567   0.34567                                 
-.1234   -0.1234                                 
**       0                                       
0.34.62  0.34                                    
24Days   24                                      
42ab     42                                      
54-3     54                                      
Victor H
  • 69
  • 4
0

Best method seems to be the function solution but if you don't have necessary privileges in the environment you are struggling (like me), then you can try this one:

SELECT
 CASE
  WHEN
     INSTR(TRANSLATE('123O0',
                     ' qwertyuıopğüasdfghjklşizxcvbnmöçQWERTYUIOPĞÜASDFGHJKLŞİZXCVBNMÖÇ~*\/(){}&%^#$<>;@€|:_=',
                     'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
                     ),
       'X') > 0
  THEN 'Y'
  ELSE 'N'
END is_nonnumeric
FROM DUAL

By the way: In my case the problem was due to "," and "." :) So take that into consider. Inspired from this one. Also this one seems more concise.

By the way 2: Dear Oracle, can you please create some built-in functions for such small but invaluable needs?

Gultekin
  • 119
  • 1
  • 3
  • It is present already: [DEFAULT on ERROR](https://stackoverflow.com/a/45886745/5070879) and [VALIDATE_CONVERSION](https://stackoverflow.com/a/45886915/5070879) – Lukasz Szozda Nov 11 '19 at 14:29