Skip to Main Content

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, phil.

Asked: February 02, 2004 - 7:44 am UTC

Last updated: August 28, 2013 - 7:22 pm UTC

Version: 8.1.7

Viewed 10K+ times! This question is

You Asked

Tom

I have a control table that contains various values, numeric, character and dates. They are all stored as VARCHARs but when a value is numeric, can I perform any easy check so that users do not enter illegal values? The table exists like this because we are still adding in new parameters for the app.

In VB, there is a function "isnumeric()" which will accomplish this but does not seem to be the same in pl/sql?

Thanks, Phil

and Tom said...

just assign the varchar2 to a number and catch any exceptions. An exception indicates "not a number"


...
begin
......
BEGIN
l_test_number := l_string_that_might_hold_a_number;
.... code that should run when it is in fact a number......
EXCEPTION
WHEN OTHERS
then
..... code that should run when it is in fact NOT a number.....
END;
.......
end;



Rating

  (22 ratings)

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

Comments

simple, instant response

A reader, February 02, 2004 - 8:13 am UTC

Thanks, couldn't be more precise!

Perhaps I'm being picky but...

Mike, February 02, 2004 - 8:18 am UTC

I seem to remeber some highly regarded Oracle person saying that it is a bug if you use a "when others" that is not followed by a raise...

Shurley it should be a "when invalid number" and or when numeric or value error that should be used, or you could get any other error and wrongly assume that its because it is not a number ;)

Tom Kyte
February 02, 2004 - 9:24 am UTC

I said it almost certainly is a bug -- not 100%, almost certainly :)

Here -- we are in FACT expecting an exception to be raised

AND

we can deal with it

MEANING

it isn't an error but an expected "condition" -- like NO_DATA_FOUND. There are two exceptions that could be raised -- hence the use of when others here -- since the ONLY thing that could be going wrong here is in fact "invalid number".

Being picky...

Mark A. Williams, February 02, 2004 - 9:25 am UTC

Surely you did not mean "shurley"? (My typing is not better, btw)

But, on a more serious note, who is to say that "...code that should run when it is in fact NOT a number..." does not include a RAISE?

- Mark

I did mean Shurley

A reader, February 02, 2004 - 9:40 am UTC

I was refering to a "Shurley Shome Mishtake" from Private Eye a satirical mag that you probably don't see a lot of outside of britain (but should)

however I do agree that any of the code included could raise an error and cause the code to switch to thinking it's not a valid number. another block would be needed. but I think this is getting quite pedantic now...

Shurley!

Mark A. Williams, February 02, 2004 - 9:46 am UTC

My mistake (I should know better - my wife is from Buckinghamshire). Anyway, pedantic can be good sometimes too! (But I agree)

Cheers,

Mark

What is a number

Fan, February 02, 2004 - 12:39 pm UTC

I recall in another forum a discussion about creating a function to determine if a value is a number, and in the end, we decided, only the customer/coder can determine what a number is

It came down to creating test data (and a fat finger), to discover that not all numbers look like numbers. e.g., take the values 1500, 15e00 and 15.00 we know that they are numbers (not all necessarily look like numbers). The question is:

Does the customer want them ALL treated as numbers?



I beg to differ...

Mike, February 20, 2004 - 9:58 am UTC

Perhaps it;s because it's Friday, but I seem to be deluded into thinking I might be right.

Consider...

declare
l_string varchar2(10) := '123';
l_test_number number;
begin
begin
dbms_output.put_line('Examine String');
l_test_number := l_string;
dbms_output.put_line('It is a number');
--code that should run when it is in fact a number......
--
-- lots of lots of code, perhpas it inserts into a table and violates a check constraint or PK? or as
--below does a select into that raises a NO_DATA_FOUND
raise no_data_found;
exception
when others
then
dbms_output.put_line('It is not a number');
--code that should run when it is in fact not a number.....
end;
--
end;


How can this code not "go wrong" without changing the when others? in other words how can you account for an exception being raised after the check and it being handled correctly?? set some flags to know you went past the number check? The best I can think of is

declare
l_string varchar2(10) := '123';
l_test_number number;
begin
begin
dbms_output.put_line('Examine String');
l_test_number := l_string;
dbms_output.put_line('It is a number');
--code that should run when it is in fact a number......
--
-- lots of lots of code, perhpas it inserts into a table and violates a check constraint or PK? or as
--below does a select into that raises a NO_DATA_FOUND
raise no_data_found;
exception
when others
then
-- we must test both varsincase l_string
-- was passed as null and we then raised an
-- error, null is a valid number
if l_test_number is not null
or l_string is null
then
raise;
else
dbms_output.put_line('It is not a number');
--code that should run when it is in fact not a number.....
end if;
end;
--
end;


and I'm not really a fan of that ;)


Tom Kyte
February 20, 2004 - 10:12 am UTC

You are right, it was a bit sloppy -- i would normally create a function "is_number" that returns boolean but short of that,

declare
l_string varchar2(10) := '123';
l_test_number number;
l_is_number boolean := true;
begin
begin
l_test_number := l_string;
exception
when others then
l_is_number := FALSE;
end;

if ( l_is_number )
then
--code that should run when it is in fact a number......
--
else
--code that should run when it is in fact not a number.....
end;
end;


would be "safe"

Check if numeric

Martin, February 20, 2004 - 11:03 am UTC

Hi,

Although not very readable this seems to work fine:

replace(translate(<value>,'N01234567890','XNNNNNNNNNN'),'N',null)

If this expression is null then the value is a number, otherwise it's not.


Tom Kyte
February 20, 2004 - 1:07 pm UTC

a whole number sure, what about -1.12543

Vicky, March 11, 2004 - 8:03 pm UTC

This should do for negative and numbers with decimal points.

replace(replace(replace(translate(contact_value,'N01234567890','XNNNNNNNNNN'),'N',null),'-'),'.')

What about values like 1.2.3 or --1

A reader, March 19, 2004 - 12:30 am UTC

cheers

Tom Kyte
March 19, 2004 - 8:57 am UTC

use the exception block, it'll catch absolutely EVERYTHING.

alternate idea

Tim W, March 29, 2004 - 5:54 pm UTC

This is more of an algorithm than oracle code, because i have only been working with oracle for about a month now. But it should take any alphanumeric string and report if it is a number.

Given T is a string of characters.

if (length(T) = length(replace(T,'abcdefghijklmnopqrstuvyxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()_+=`[]{}\|;:,<>/?'))
{
// the string contains only 0-9 . and -
if( (length(T) - length(replace(T,'.'))) <= 1)
{
// the string contains only 0 or 1 "."
if( (length(T) - length(replace(T,'-')) ) = 0) OR ( (length(T) - length(replace(T,'-')) = 1) AND substr(T,1,1) = "-" )
{
// the string contains 0 or 1 "-" and if so it is in the first position
execute true statements here
}
}
}

the reason for this one is that the prior sugestion will accept 9-9.-9.9 as a valid string which it is not. because a number may contain no more than one "." and no more than one "-" which must be in the first position only. thus comparing the lengths of the replaced strings.




Tom Kyte
March 29, 2004 - 6:15 pm UTC

how about:


declare
l_num number;
begin
l_num := to_number(T);
code for when t is a number
exception
code for when t is not a number
end;


or even "easier"

create function isnumber( p_string in varchar2 ) return boolean
is
l_number number;
begin
l_number := P_string;
return true;
exception
when others then return false;
end;
/



begin
if (isnumber(T))
then
.....


Built in function

Tim W, March 30, 2004 - 8:49 am UTC

Well if there is a built in function use that. No need to re-invent the wheel. The impression i got was that there wasnt one built in.

Tom Kyte
March 30, 2004 - 9:03 am UTC

declare
l_num number;
begin
l_num := to_number(T);
code for when t is a number
exception
code for when t is not a number
end;

"to_number" is the builtin. 'isnumber' makes it easier to use...

One simple question

A reader, January 26, 2006 - 9:38 am UTC

Tom,


I have a column that I would like to provent the users
from putting decimals it must be whole number. Is there
anyway I can accomplish that?

Thanks you.

Tom Kyte
January 26, 2006 - 10:25 am UTC

use number(n)


no decimals allowed - it'll round the number upon input.


if you want to fail the transaction, you can just use number and add a check constraint to the column:

check ( x = trunc(x) )




Check constraint?

Mike Walsh, January 27, 2006 - 12:58 am UTC

create table foon
(bar number(3) check (bar=trunc(bar)));

-- Table created.

insert into foon(bar) values (25.2323);

-- 1 row created.

select bar from foon;

-- BAR
-- ----------
-- 25


select 1 from dual where 21=trunc(21);

-- 1
-- ----------
-- 1

select 1 from dual where 21.0000001=trunc(21.0000001);

-- no rows selected


select 1 from dual where
21.00000000000000000000000000000000000000001=
trunc(21.00000000000000000000000000000000000000001);

-- 1
-- ----------
-- 1

set serverout on size 100000

create or replace trigger foon_ck before insert on foon for each row
begin
if (:new.bar <> trunc(:new.bar)) then
raise_application_error(-20002,'Toast');
else
dbms_output.put_line('Value of bar[' || :new.bar || ']');
end if;
end foon_ck;
/
show errors;

-- Trigger created.
-- No errors.


insert into foon(bar) values (25.2323);

-- Value of bar[25]

-- 1 row created.


--------

Please excuse the quick and dirty example, but what am I
missing?

Clearly there's a rounding issue to deal with also, but
both the check and trigger seem to access the post-rounded
value...



Tom Kyte
January 27, 2006 - 8:36 am UTC

you made it a number(3), that is going to round (as I stated) 


I said "use number(n)" and it'll round

OR

I said "just use number and this check constraint"


ops$tkyte@ORA9IR2> create table t
  2  ( x number(3),
  3    y number check (y=trunc(y))
  4  )
  5  /

Table created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t values ( 1.2, null );

1 row created.

ops$tkyte@ORA9IR2> insert into t values ( null, 1.2 );
insert into t values ( null, 1.2 )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.SYS_C003710) violated


ops$tkyte@ORA9IR2> select * from t;

         X          Y
---------- ----------
         1

 

Reading too late... but raises another question.

Mike Walsh, January 27, 2006 - 11:07 am UTC

Sorry, Tom. I should read more carefully. A little too subtle
a difference for me after midnight.

I'm wondering if you can explain this related plsql issue:

create table foon
(bar number(3));

-- Table created.


set serverout on size 100000

create or replace procedure rounded(val in foon.bar%type)
as
val2 foon.bar%type;
begin
dbms_output.put_line(val);
begin
val2 := val;
dbms_output.put_line(val2);
exception
when others then
dbms_output.put_line(sqlcode || '-' || sqlerrm);
end;
end rounded;
/

-- Procedure created.

exec rounded(25.2323);

-- 25.2323
-- 25

-- PL/SQL procedure successfully completed.


exec rounded(25000);

-- 25000
-- -6502-ORA-06502: PL/SQL: numeric or value error: number precision too large

-- PL/SQL procedure successfully completed.


Why do the two usages get treated differently? I have a
guess (you get a chance at error recovery with internal
values vs. arguments), but I'm not sure.

Thanks.


Tom Kyte
January 27, 2006 - 11:32 am UTC

all parameters types in plsql are unconstrained types - I concurr the documentation does not relate this very well, but they are unconstrained types - always.

unconstrained types means..?

A reader, January 27, 2006 - 11:56 am UTC

What does that mean actually? Does it mean that even if we define a variable as number(3), pl/sql will take it as just number without a limit?

Tom Kyte
January 28, 2006 - 12:08 pm UTC

No, a variable is constrained, parameters - are not.


ops$tkyte@ORA10GR1> create table t (x number(3) );

Table created.

ops$tkyte@ORA10GR1> create or replace procedure p( unconstrained t.x%type )
  2  as
  3          constrained t.x%type := 1.2;
  4  begin
  5          dbms_output.put_line( unconstrained );
  6          dbms_output.put_line( constrained );
  7  end;
  8  /

Procedure created.

ops$tkyte@ORA10GR1> exec p( 1.2 )
1.2
1

PL/SQL procedure successfully completed.
 

unconstrained...

Mike Walsh, January 27, 2006 - 12:31 pm UTC

He means that arguments are typed but not sized, which is
how you define them without the "%type":

procedure do_stuff(count in number,
tags in varchar2,
sized in number(3)) <-- this would fail

whereas with

procedure do_stuff(count in number,
tags in varchar2,
sized in foon.bar%type)

the argument "sized" implicitly gets rewritten as

sized in number

rather than
sized in number(3)

which would actually match the table. It's consistent
with the way it's documented (Ch 8 - PL/SQL Subprograms:
"You cannot constrain the datatype of a parameter.")

It's just not obvious (at least not to me), especially
when inside the procedure it is constrained.

I often forget about constraints, since

procedure do_stuff(count in number)
as
v_other number;
...

is fine, but

procedure do_stuff(msg in varchar2)
as
v_other varchar2;
...

complains that

Error: PLS-00215: String length constraints must be in range (1 .. 32767)

Of course it's because number has a default size and
varchar2 doesn't, but still, it doesn't scan very well...



To Mike

Greg, January 27, 2006 - 12:52 pm UTC

Great job explaining that. You gave clear examples and references to the documentation. Well Done!!

Any easy way to handle formatted numbers?

Greg, February 15, 2006 - 9:53 am UTC

gregs-DEV > create or replace function isnumber( p_string in varchar2 ) return boolean
2 is
3 l_number number;
4 begin
5 l_number := P_string;
6 return true;
7 exception
8 when others then return false;
9 end;
10 /

Function created.

gregs-DEV >
gregs-DEV > set serverout on
gregs-DEV >
gregs-DEV > begin
2 if isnumber('45,456.56') then
3 dbms_output.put_line ( 'Number' );
4 else
5 dbms_output.put_line ( 'Not a number' );
6 end if;
7 end;
8 /
Not a number

PL/SQL procedure successfully completed.


??
'45,456.56' is a number .. it's just formatted .. the coma's confusing it ...

If I change the function to:
5 l_number := to_number ( P_string, '999,999.99' );

it works .. but then '45456.56' doesn't work!

How can I "have my cake, and eat it too"?? ;)

(and ... I don't really want to strip out comas, because: "34,34,34.45" isn't a number . it looks more like a list of numbers ... )

or is it simply: "we gotta know the format" ??


Tom Kyte
February 15, 2006 - 11:45 am UTC

could do something like this:

ops$tkyte@ORA10GR2> create or replace function isnumber( p_string in varchar2 ) return boolean
  2  is
  3          not_number exception;
  4          pragma exception_init( not_number, -6502 );
  5          l_number number;
  6          l_return boolean := FALSE;
  7  begin
  8          if (instr(p_string,',') > 0 )
  9          then
 10                  l_number := to_number( p_string, '999g999g999g999g999g999d999999999999999999' );
 11          else
 12                  l_number := to_number( p_string );
 13          end if;
 14          return TRUE;
 15  exception
 16          when not_number
 17          then
 18                  return FALSE;
 19  end;
 20  /
 
Function created.
 
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> declare
  2          type array is table of varchar2(255);
  3          l_data array := array( '123456.01', '123,456.01', '12,34,56', 'abc' );
  4  begin
  5          for i in 1 .. l_data.count
  6          loop
  7                  if ( isnumber( l_data(i) ) )
  8                  then
  9                          dbms_output.put_line( l_data(i) || ' yes' );
 10                  else
 11                          dbms_output.put_line( l_data(i) || ' no' );
 12                  end if;
 13          end loop;
 14  end;
 15  /
123456.01 yes
123,456.01 yes
12,34,56 no
abc no
 
PL/SQL procedure successfully completed.
 

*blink*

Greg, February 15, 2006 - 1:08 pm UTC

Right ... for some reason I was trying to challenge myself and not use an IF or INSTR statement ... ;p

Silly me ...

Thanks!!


Tom Kyte
February 15, 2006 - 1:22 pm UTC

we could use CASE and REPLACE instead if you like ;)

Please try this

ROCKY, August 28, 2013 - 8:03 am UTC

Step 1:- Create a function like this

CREATE OR REPLACE function isnumber(a varchar2)
    return varchar2 is
begin
    -- Coded by ROCKY
    for i in 1..length(a) loop
    if not((ascii(substr(a,i,1))>=48)  and  
         (ascii(substr(a,i,1))<=57))  then
         return 'F';
    end if;
    end loop;
    return 'T';
end isnumber;


USAGE like this
select isnumber('ASDV2') from dual;

Return value 'T' = is a number
Return value 'F' = is not a number

STEP 2: -
select
    regexp_like('039870931.00j','[^0-9.]','m') regexp,
    DECODE(TRIM(TRANSLATE(replace('039870931.00h','.',''),'0123456789',' ')), NULL, '0','1') decod
from dual ; 



Tom Kyte
August 28, 2013 - 7:22 pm UTC

why would rocky code something that doesn't work in general?

ops$tkyte%ORA11GR2> select isnumber( '1e100' ) isn, to_number( '1e100' ) n from dual;

ISN                 N
---------- ----------
F          1.000E+100

ops$tkyte%ORA11GR2> select isnumber( '1.2' ) isn, to_number( '1.2' ) n from dual;

ISN                 N
---------- ----------
F                 1.2


rocky should use the code that was provided above.... it works for all Oracle numbers.

simple PL/SQL piece to do it

Waleed, September 15, 2016 - 9:07 am UTC

CREATE OR REPLACE FUNCTION isitreallyanumber (p_srting VARCHAR2)
RETURN VARCHAR2
IS
dummy NUMBER;
BEGIN
dummy := TO_NUMBER (NVL (p_srting, 'a null value!!')); -- null values are not a number, we examine only numbers here.
RETURN 'Y';
EXCEPTION
WHEN OTHERS
THEN
RETURN 'N';
END;

--test

SELECT isitreallyanumber ('') FROM DUAL;
SELECT isitreallyanumber ('12aads') FROM DUAL;
SELECT isitreallyanumber ('4234') FROM DUAL;') FROM DUAL;