如何使用非默认 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:如何使用非默认实体管理器对用户进行身份验证?