【问题标题】:"Safe" TO_NUMBER()“安全”TO_NUMBER()
【发布时间】:2011-05-28 01:57:28
【问题描述】:
SELECT TO_NUMBER('*') FROM DUAL

这显然给了我一个例外:

ORA-01722: 无效号码

有没有办法“跳过”它并改为获取0NULL

整个问题:我有 NVARCHAR2 字段,其中包含数字而不是几乎 ;-)(如 *),我需要从列中选择最大的数字。

是的,我知道这是一个糟糕的设计,但这正是我现在需要的...... :-S

UPD

我自己解决了这个问题

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

【问题讨论】:

  • *是您唯一希望遇到的角色吗?
  • @OMG Ponies:不...我用丑陋的REGEXP_SUBSTR(field, '^\d+')解决了这个问题

标签: oracle type-conversion ora-01722


【解决方案1】:

我找不到比这更好的了:

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;

【讨论】:

  • 是的,我也决定写我自己的函数,和你的有点不同(因为54-3应该变成54,我没有提到,抱歉)。
  • 这里最好只忽略 ORA-01722。
  • 考虑到custom pl/sql functions have significant performace overhead 可能不适合繁重的查询。
  • 请注意下面的答案,显示如何从 12c r2 开始以本机方式处理此问题stackoverflow.com/a/45886745/587365
  • 我同意米兰关于只忽略 ORA-01722 的观点,而是写 exception when invalid_number then return 0;
【解决方案2】:

滚动您自己的正则表达式来测试数字可能有点混乱,但下面的代码可能会起作用。我认为 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

【讨论】:

    【解决方案3】:

    适合原始问题和相当老的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
    )
    

    【讨论】:

    • @Ronnis,内置函数通常是,但正如你所说,它们表现良好。在 Oracle7 附近工作(从内存中)
    • 这将从字符串54-3 生成543。我没有在问题中提到它,但在这种情况下,我希望 54 (这就是我的正则表达式解决方案所做的)。
    • @zerkins,实际上认为它会从 54-3 产生 0,无论如何,正如我所说的在 cmets 之前回答原始问题。不过,就像您的最终解决方案一样。
    • 如果 number 包含空格则失败:select a, decode(trim(translate(b,'0123456789.',' ')),null,to_number(b),0) from ( select '1' a, '12 34' b 来自双 )
    • 无法在我的应用程序中使用正则表达式,所以效果很好
    【解决方案4】:
    COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+(\.\d+)?')), 0) 
    

    还将获得比例 > 0 的数字(小数点右侧的数字)。

    【讨论】:

    • 这是一个很好的解决方案,但是应该针对每个特定场景调整正则表达式。这个正则表达式可能会更好:^\-?\d*\.?\d*$ 允许负数和以. 开头的数字,并且不允许非数字尾随字符。如果field 包含1x,则^\d+(\.\d+)? 正则表达式产生1,并且0' if field` 包含0.0.1,如果field 包含0 6,则0
    【解决方案5】:
    select COALESCE(TO_NUMBER(REGEXP_SUBSTR( field, '^(-|+)?\d+(\.|,)?(\d+)?$')), 0) from dual;
    

    它将123转换为123,但123a12a3转换为0.

    【讨论】:

      【解决方案6】:
      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 等。而且我这里没有使用正则表达式。

      【讨论】:

        【解决方案7】:

        Oracle Database 12c Release 2 你可以使用TO_NUMBERDEFAULT ... 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;
        

        db<>fiddle demo

        【讨论】:

        • 优秀的解决方案。是的,已确认:版本 12.1.0.2.0 - 还不支持此功能
        【解决方案8】:

        结合以前的解决方案(来自@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                                      
        

        【讨论】:

          【解决方案9】:

          最好的方法似乎是函数解决方案,但如果你在苦苦挣扎的环境中没有必要的特权(比如我),那么你可以试试这个:

          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
          

          顺便说一句:在我的情况下,问题是由于“,”和“。” :) 所以考虑到这一点。灵感来自this one。另外this one 看起来更简洁。

          顺便问一下 2:亲爱的 Oracle,您能否为这些小而宝贵的需求创建一些内置函数?

          【讨论】:

          猜你喜欢
          • 2014-01-29
          • 2017-12-19
          • 2016-09-14
          • 1970-01-01
          • 2021-07-22
          • 2018-08-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多