如何使用非默认 NLS_NUMERIC_CHARACTERS 在 Oracle PL/SQL 中有效地将文本转换为数字?

Posted

技术标签:

【中文标题】如何使用非默认 NLS_NUMERIC_CHARACTERS 在 Oracle PL/SQL 中有效地将文本转换为数字?【英文标题】:How to efficiently convert text to number in Oracle PL/SQL with non-default NLS_NUMERIC_CHARACTERS? 【发布时间】:2010-11-10 09:22:55 【问题描述】:

我正在尝试找到一种有效、通用的方法来在 PL/SQL 中从字符串转换为数字,其中 NLS_NUMERIC_CHARACTERS 设置的本地设置是不可预测的——最好我不会碰它。输入格式是编程标准“123.456789”,但小数点两边的位数未知。

select to_number('123.456789') from dual;
  -- only works if nls_numeric_characters is '.,'

select to_number('123.456789', '99999.9999999999') from dual;
  -- only works if the number of digits in the format is large enough
  -- but I don't want to guess...

to_number 接受第三个参数,但在这种情况下,您也需要指定第二个参数,并且“默认”没有格式规范...

select to_number('123.456789', null, 'nls_numeric_characters=''.,''') from dual;
  -- returns null

select to_number('123.456789', '99999D9999999999', 'nls_numeric_characters=''.,''') from dual;
  -- "works" with the same caveat as (2), so it's rather pointless...

还有另一种使用 PL/SQL 的方法:

CREATE OR REPLACE
FUNCTION STRING2NUMBER (p_string varchar2) RETURN NUMBER
IS
  v_decimal char;
BEGIN
  SELECT substr(VALUE, 1, 1)
  INTO v_decimal
  FROM NLS_SESSION_PARAMETERS
  WHERE PARAMETER = 'NLS_NUMERIC_CHARACTERS';
  return to_number(replace(p_string, '.', v_decimal));
END;
/

select string2number('123.456789') from dual;

正是我想要的,但如果你在查询中多次执行它似乎效率不高。您无法缓存 v_decimal 的值(获取一次并存储在包变量中),因为它不知道您是否更改了 NLS_NUMERIC_CHARACTERS 的会话值,然后它会再次中断。

我是否忽略了什么?还是我太担心了,而 Oracle 这样做的效率比我认为的要高得多?

【问题讨论】:

更难的例子是转换'12.34E5'。我发现没有格式可以接受,但是单参数to_number 解析它没有问题。 【参考方案1】:

以下应该有效:

SELECT to_number(:x, 
                 translate(:x, '012345678-+', '999999999SS'), 
                 'nls_numeric_characters=''.,''') 
  FROM dual;

它将使用高效的translate 构建正确的第二个参数999.999999,因此您不必事先知道有多少位数。它将适用于所有受支持的 Oracle 数字格式(显然在 10.2.0.3 中最多 62 位有效数字)。

有趣的是,如果您有一个非常大的字符串,那么简单的to_number(:x) 将起作用,而此方法将失败。

编辑:感谢sOliver,支持负数。

【讨论】:

完成to_number函数的'fmt'参数可能有用:translate(:x, '0123456789.,-+', '9999999999DG')。这会在负数上停止 ORA-01481 错误。【参考方案2】:

如果您在每个会话中做大量工作,一个选项可能是使用 ALTER SESSION SET NLS_NUMERIC_CHARACTERS = '.,' 在您的任务开始时。

当然,如果在同一会话中执行许多其他代码,您可能会得到奇怪的结果 :-) 但是,我们可以在数据加载过程中使用这种方法,因为我们有专门的程序,它们有自己的连接池来加载数据。

【讨论】:

【参考方案3】:

对不起,我后来注意到你的问题是相反的。然而值得注意的是,对于相反的方向,有一个简单的解决方案:

有点晚了,但今天我注意到特殊格式掩码 'TM9' 和 'TME' 被描述为“文本最小数字格式模型返回(以十进制输出)可能的最小字符数。”在https://docs.oracle.com/cloud/latest/db112/SQLRF/sql_elements004.htm#SQLRF00210。

似乎 TM9 就是为了解决这个特殊问题而发明的:

select to_char(1234.5678, 'TM9', 'NLS_NUMERIC_CHARACTERS=''.,''') from dual;

结果是'1234.5678',没有前导或尾随空格,并且是一个小数点,尽管我的环境包含NLS_LANG=GERMAN_GERMANY.WE8MSWIN1252,这通常会导致一个小数逗号。

【讨论】:

【参考方案4】:
select to_number(replace(:X,'.',to_char(0,'fmd'))) from dual;

顺便说一句

select to_number(replace('1.2345e-6','.',to_char(0,'fmd'))) from dual;

如果你想要更严格

select to_number(translate(:X,to_char(0,'fmd')||'.','.'||to_char(0,'fmd'))) from dual;

【讨论】:

@JammyDodger 它确实有效并回答了问题。很高兴为此获得-1分。从你那里,我问。酷【参考方案5】:

位数不限是否现实? 如果我们假设它是,那么不是更仔细地研究需求的好理由吗?

如果我们在初始字符串超长时遇到这种奇妙的情况,那么以下方法可以解决问题:

select 
  to_number(
    '11111111.2222'
  , 'FM' || lpad('9', 32, '9') || 'D' || lpad('9', 30, '9')
  , 'NLS_NUMERIC_CHARACTERS=''.,'''
  ) 
from 
  dual

【讨论】:

以上是关于如何使用非默认 NLS_NUMERIC_CHARACTERS 在 Oracle PL/SQL 中有效地将文本转换为数字?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用不同的数据库设置(非默认)启动 Django 开发服务器

如何以编程方式更改非默认声音设备的音量?

Symfony2:如何使用非默认实体管理器对用户进行身份验证?

使用 SqlBulkCopy,如何将数据插入到非默认数据库架构中的表中?

c ++如何将类初始化器用于非默认构造的类

如何使用非默认运行参数在 AWS Elastic Beanstalk 中运行 Docker 容器?