35

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?

Doug Porter
  • 7,721
  • 4
  • 40
  • 55
Fgblanch
  • 5,195
  • 8
  • 37
  • 51
  • 2
    possible duplicate of [Simple PL/SQL function to test if a string is a number](http://stackoverflow.com/questions/19572759/simple-pl-sql-function-to-test-if-a-string-is-a-number) – Sebastian Zartner Aug 26 '14 at 12:10
  • 1
    If you are not restrained by Oracle version, there is built-in [VALIDATE_CONVERSION](https://stackoverflow.com/a/45886915/5070879) function – Lukasz Szozda Nov 15 '19 at 21:11

16 Answers16

66

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.

Saish
  • 1,518
  • 1
  • 12
  • 21
32

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.

Jon Heller
  • 34,999
  • 6
  • 74
  • 132
Justin Cave
  • 227,342
  • 24
  • 367
  • 384
  • 1
    Why not using 1 and 0 instead of 'Y' and 'N'? PL/SQL has the NUMBER data type and SQL will accept 1 and 0 in the query. – eyettea Nov 05 '13 at 13:51
  • 3
    @eyetea - Personal preference. You can certainly return a 0 or a 1. If the developers are all English speaking, I tend to prefer a Y and N instead because that's a bit more self-documenting. If you're dealing with more international development, a 0 and 1 makes more sense by getting language out of the mix. – Justin Cave Nov 05 '13 at 15:56
  • I am just saying that in the case of 0 and 1, it wouldn't be necessary to write is_number( myTable.id ) = 1, but is_number( myTable.id ) would be enough! – eyettea Nov 05 '13 at 16:22
  • 1
    @eyetea - That is not true. In PL/SQL, 0 is not implicitly false and 1 is not implicitly true. You would still need to have the `= 1` part of the expression in order to produce a boolean result. – Justin Cave Nov 05 '13 at 16:29
  • Justin, do you know if the way you answered here, or Saish's answer is actually more efficient with large datasets (Oracle 11g if environment is important)? – Reimius Mar 21 '14 at 20:27
  • 1
    @Reimius - It will depend on how frequently the data is actually numeric. Exceptions are generally expensive so if most of the data is non-numeric, this approach is going to be somewhat inefficient. If most of the data is numeric, on the other hand, this function is pretty efficient. This also takes care of non-integer numbers which Saish's regular expression does not and does not allow non-numbers like `1.234.567.890` like kevlened's solution. And it respects whatever your session's decimal separator is. – Justin Cave Mar 21 '14 at 20:35
  • I was not expecting that this function returns true (or Y or 1 or whatever) on a NULL input: select is_number(null) from dual; -- returns 'Y' – leqid Nov 06 '20 at 22:10
17

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).

Matt Byrne
  • 4,908
  • 3
  • 35
  • 52
  • In order to include negative numbers the expression should be where REGEXP_LIKE (foo,'^-?\d+(\.\d+)?$'); – shonky linux user Jan 20 '14 at 01:05
  • Yea good point - I pasted in my regexp for something I was doing where I only wanted positive numbers. Will update my answer - love the name btw shonky ;-) – Matt Byrne Jan 20 '14 at 04:53
  • What about `.1`, `+1`, `1234f`, or `1e1`? Not all of those are commonly considered "numeric", but they are all valid numbers to Oracle. – Jon Heller Jan 25 '14 at 05:31
  • If they are "numeric to Oracle" then they would be stored numeric. Since the question is about a `VARCHAR`/`VARCHAR2`/whatever then the question is more about the system/code that generated the number and not necessarily Oracle itself (imagine a world outside Oracle). In this case you can be as pedantic as you like or try to suit the use case at hand. Comment was fair, downvote was unnecessary. – Matt Byrne Jan 27 '14 at 21:21
  • Need this one voted up, as this is the best answer here! – Tomasz Apr 01 '14 at 03:14
6

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.

Community
  • 1
  • 1
kevlened
  • 10,846
  • 4
  • 23
  • 17
  • I used this solution in a simplified variation:
    `SELECT foo FROM bar WHERE TRANSLATE(your_number, 'X0123456789','X') IS NULL`
    Note that the X does not get translated. It is used only because the second string must not be an empty string.
    – not2savvy Apr 26 '16 at 10:38
4

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
benRollag
  • 1,219
  • 4
  • 16
  • 21
Kanu
  • 57
  • 1
  • 1
2

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.

TheBakker
  • 2,852
  • 2
  • 28
  • 49
2

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
  • 1
    `invalid_number` is the wrong exception. That is the exception for a SQL statement, but in PL/SQL the exception is `value_error`. – Jon Heller Apr 24 '14 at 17:34
1
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.

Waqas Ashraf
  • 759
  • 5
  • 4
1

@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.

Pancho
  • 2,043
  • 24
  • 39
  • I'm glad you mentioned the purity rules in your original answer. It reminded me of problems I've seen when using these functions in parallel SQL, and helped to improve the accepted answer by adding `DETERMINISTIC` and `PARALLEL_ENABLE`. However, this version is not any "purer" than the original function. Write No Package State does not apply since there are no package variables involved. And simply being "purer" does not help, it must be declared in order to allow features like parallelism to work. – Jon Heller Apr 26 '14 at 20:33
  • @jonearles - justin's solution contains a variable "l_num" which I interpreted as a "package variable" however on testing I see that the assertion is not affected by the function variable. – Pancho Apr 27 '14 at 12:30
  • @jonearles - I have a few responses I will break into different comments 1. Justin's solution contains a variable "l_num" which I interpreted as a "package variable" however on testing I see that the assertion is not affected by the function variable - so it is indeed no purer. – Pancho Apr 27 '14 at 12:36
  • @jonearles regarding your DETERMINISTIC observation: there is a difference between fulfillment and implementation. Implementation is optional per use case but as it is impossible without fulfillment, the fulfillment to which I am referring is critical. Also, my function is in a package body and the assertion is done in the specification via the RESTRICT_REFERENCES pragma. – Pancho Apr 27 '14 at 12:43
  • @jonearles - even though my function is indeed no purer, it is still my preferred choice as there is no requirement for definition of, and specific memory allocation to, the essentially irrelevant l_num variable contained in Justin's solution. – Pancho Apr 27 '14 at 12:47
  • Sheesh, I am learning a lot from this ostensibly trivial piece of functionality. Just to confirm for all, RESTRICT_REFERENCES, while fully functional is deprecated as of 11g in favour of DETERMINISTIC and PARALLEL_ENABLE as used by @jonearles. I thus added this to the end of the declaration for clarity. – Pancho Apr 28 '14 at 09:23
  • A brief summary: While the above function is conceptually identical to the solution offered by Justin, as explained above I prefer it because there is no need for the irrelevant l_num variable. Is this method better than regexp?; well, "for any number" validation it is safer, as Oracle handles the logic. This does not render the regexp approach incorrect however; just be very aware that emulating Oracle's number handling isn't straight forward, particularly when factoring in internationalisation. As an aside, the regexp approach remains very useful when testing for specific numeric subsets. – Pancho Apr 28 '14 at 09:48
  • @jonearles: For interest, as at 11g, there is no requirement to include DETERMINISTIC PARALLEL_ENABLE in the function definition if they are included in the function declaration. So this pertains to forward declaration and package specification / body implementations. – Pancho Apr 28 '14 at 18:45
1

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.

tbone
  • 15,107
  • 3
  • 33
  • 40
0

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....

Michael Broughton
  • 4,045
  • 14
  • 12
  • 2
    A function returning a boolean would work well if you only need to do the check in PL/SQL. But a function returning a boolean cannot (regrettably) be called from a SQL query because the SQL engine does not have a boolean data type. – Justin Cave Feb 22 '11 at 18:31
  • Yep, thats what I get for diving in without testing! ;) – Michael Broughton Feb 22 '11 at 18:34
0

You can use this example

SELECT NVL((SELECT 1 FROM  DUAL WHERE   REGEXP_LIKE (:VALOR,'^[[:digit:]]+$')),0) FROM DUAL;
roschach
  • 8,390
  • 14
  • 74
  • 124
-1

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;
-1

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
Community
  • 1
  • 1
Vadzim
  • 24,954
  • 11
  • 143
  • 151
-1

if condition is null then it is number

IF(rtrim(P_COD_LEGACY, '0123456789') IS NULL) THEN
                return 1;
          ELSE
                return 0;
          END IF;
Chris Trudeau
  • 1,427
  • 3
  • 16
  • 20
-1

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.

mach128x
  • 320
  • 3
  • 13