"Безопасный" TO_NUMBER () - PullRequest
       49

"Безопасный" TO_NUMBER ()

49 голосов
/ 20 декабря 2010
SELECT TO_NUMBER('*') FROM DUAL

Это, очевидно, дает мне исключение:

ORA-01722: неверный номер

Есть ли способ "пропустить" его и получить 0 или NULL вместо?

Весь вопрос: у меня есть поле NVARCHAR2, которое содержит цифры, а не почти ;-) (например, *), и мне нужно выбрать наибольшее число из столбца.

Да, я знаю, что это ужасный дизайн, но это то, что мне сейчас нужно ...: -S

UPD :

Для себя я решил эту проблему с

COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+')), 0)

Ответы [ 9 ]

26 голосов
/ 27 апреля 2013
COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+(\.\d+)?')), 0) 

также получит числа со шкалой> 0 (цифры справа от десятичной точки).

15 голосов
/ 20 декабря 2010

Я не мог найти ничего лучше, чем это:

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;
13 голосов
/ 25 августа 2017

С Oracle Database 12c Release 2 вы можете использовать TO_NUMBER с DEFAULT ... ON CONVERSION ERROR:

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

или CAST:

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

дБ <> демонстрация скрипки

10 голосов
/ 25 июня 2014
select COALESCE(TO_NUMBER(REGEXP_SUBSTR( field, '^(-|+)?\d+(\.|,)?(\d+)?$')), 0) from dual;

Преобразует 123 в 123 , но 123a или 12a3 в 0 .

7 голосов
/ 20 декабря 2010

Подгонка оригинального вопроса и довольно старая школа

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
)
1 голос
/ 03 сентября 2014
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;

Этот код исключает такие строки, как: -1-1, 1..1, 12-2 и так далее.И я не использовал здесь регулярные выражения.

1 голос
/ 20 декабря 2010

Вероятно, это немного грязно, когда вы проверяете число на собственном регулярном выражении, но приведенный ниже код может работать.Я думаю, что другое решение от Gabe, включающее пользовательскую функцию, является более надежным, поскольку вы используете встроенную функциональность Oracle (и мое регулярное выражение, вероятно, не на 100% правильно), но, возможно, оно того стоит:

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

Это даст следующий вывод:

MYNUM   IS_NUM
-------- ----------
12345   12345
54-3    
123.4567    123.4567
.34567  
-0.3462 -0.3462
0.34.62 
1243.64 1243.64
0 голосов
/ 09 марта 2018

Лучший метод - это функциональное решение, но если у вас нет необходимых привилегий в среде, с которой вы боретесь (как я), то вы можете попробовать это:

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

Кстати: в моем случае проблема была связана с "," и "." :) Так что примите это во внимание. Вдохновленный от этот . Также этот кажется более лаконичным.

Кстати, 2: Уважаемый Oracle, не могли бы вы создать несколько встроенных функций для таких небольших, но бесценных нужд?

0 голосов
/ 21 февраля 2018

Сочетание предыдущих решений (от @sOliver и @Mike Meyers) и попытка собрать как можно больше чисел, удалив последний '$' из REGEXP.

Его можно использовать для фильтрации действительного числа из таблицы конфигурации, а также добавить комментарий типа «12 дней».

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;

даст


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