如果超过 78 个字符,则使用 TIMESTAMP 的 TO_CHAR 会引发 ORA-01877
Posted
技术标签:
【中文标题】如果超过 78 个字符,则使用 TIMESTAMP 的 TO_CHAR 会引发 ORA-01877【英文标题】:using TO_CHAR of TIMESTAMP throws ORA-01877 if it's more than 78 characters 【发布时间】:2016-03-17 14:14:32 【问题描述】:我遇到了一个奇怪的问题
我正在尝试将一些数字转换为“单词”,只需这样做:
TO_CHAR(TO_TIMESTAMP(LPAD( nvl(trunc(99999999),0) , 9, '0'), 'FF9'),'FFSP') AS amt_in_words
效果很好,输出是 《九十九亿九九九九十九》
但是当我们尝试像 99999998 这样的数字时会发生什么?
TO_CHAR(TO_TIMESTAMP(LPAD( nvl(trunc(99999998),0) , 9, '0'), 'FF9'),'FFSP') AS amt_in_words
它抛出一个错误“ORA-01877: string is too long for internal buffer”
嗯,这是一个较小的数字,它应该可以工作,但它没有 我的理论是:它失败了,因为结果文本的长度是 79 个字符,并且由于某种原因它失败了
suggestion 1: 使用强制转换(EXPR as VARCHAR(100) ) 但它给出了同样的错误
有什么想法吗?
澄清: 不幸的是,我正在开发 Oracle Fusion BI buplisher,显然我不能使用任何 Pl/sql 过程,只是正常的选择查询
【问题讨论】:
nvl/lpad/trim 无关紧要,有点让人分心;你可以做TO_CHAR(TO_TIMESTAMP('099999998', 'FF9'), 'FFSP')
。奇怪的是,使用 SPTH 是可行的——不管怎样,时间长一点。无论如何......这已经作为错误 18408384 提出过,但仍然发生在 11.2.0.4;没有 12c 可以检查。
【参考方案1】:
PL/SQL 似乎没有遇到同样的问题,因此您可以编写自己的函数来进行转换:
create or replace function spell_number(p_number number) return varchar2 is
l_str varchar2(200);
begin
l_str := to_char(to_timestamp(lpad(nvl(trunc(p_number), 0) , 9, '0'), 'FF9'), 'FFSP');
return l_str;
end;
/
然后更长的值起作用:
select spell_number(99999999) from dual;
AMT_IN_WORDS
----------------------------------------------------------------------------------------------------
NINETY-NINE MILLION NINE HUNDRED NINETY-NINE THOUSAND NINE HUNDRED NINETY-NINE
select spell_number(99999998) from dual;
AMT_IN_WORDS
----------------------------------------------------------------------------------------------------
NINETY-NINE MILLION NINE HUNDRED NINETY-NINE THOUSAND NINE HUNDRED NINETY-EIGHT
select spell_number(999999999) from dual;
AMT_IN_WORDS
----------------------------------------------------------------------------------------------------
NINE HUNDRED NINETY-NINE MILLION NINE HUNDRED NINETY-NINE THOUSAND NINE HUNDRED NINETY-NINE
任何超过 9 位的数字都会忽略最低有效数字,但这似乎是 SP 格式的限制:
with t (num) as (
select 99999998 from dual
union all select 99999999 from dual
union all select 999999999 from dual
union all select 1000000000 from dual
union all select 1000000001 from dual
union all select 1000000010 from dual
union all select 9999999999 from dual
union all select 10000000000 from dual
union all select 99999999999 from dual
union all select 100000000000 from dual
)
select num, spell_number(num) as amt_in_words from t;
NUM AMT_IN_WORDS
------------- ----------------------------------------------------------------------------------------------------
99999998 NINETY-NINE MILLION NINE HUNDRED NINETY-NINE THOUSAND NINE HUNDRED NINETY-EIGHT
99999999 NINETY-NINE MILLION NINE HUNDRED NINETY-NINE THOUSAND NINE HUNDRED NINETY-NINE
999999999 NINE HUNDRED NINETY-NINE MILLION NINE HUNDRED NINETY-NINE THOUSAND NINE HUNDRED NINETY-NINE
1000000000 ONE HUNDRED MILLION
1000000001 ONE HUNDRED MILLION
1000000010 ONE HUNDRED MILLION ONE
9999999999 NINE HUNDRED NINETY-NINE MILLION NINE HUNDRED NINETY-NINE THOUSAND NINE HUNDRED NINETY-NINE
10000000000 ONE HUNDRED MILLION
99999999999 NINE HUNDRED NINETY-NINE MILLION NINE HUNDRED NINETY-NINE THOUSAND NINE HUNDRED NINETY-NINE
100000000000 ONE HUNDRED MILLION
【讨论】:
【参考方案2】:将数字分成数千组,然后分别处理:
Oracle 设置:
CREATE OR REPLACE FUNCTION TO_WORDS(
in_value IN INT
) RETURN VARCHAR2 DETERMINISTIC
AS
p_value INT := ABS( in_value );
p_words VARCHAR2(4000);
t_array CONSTANT SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
null,
' thousand ',
' million ',
' billion ',
' trillion ',
' quadrillion ',
' quintillion ',
' sextillion ',
' septillion ',
' octillion ',
' decillion ',
' undecillion ',
' duodecillion '
);
p_counter INT := 0;
BEGIN
WHILE p_value > 0 LOOP
p_counter := p_counter + 1;
IF MOD( p_value, 1000 ) > 0 THEN
p_words := TO_CHAR( TO_DATE( MOD( p_value, 1000 ), 'j' ), 'jsp' ) || t_array( p_counter ) || p_words;
END IF;
p_value := FLOOR( p_value / 1000 );
END LOOP;
RETURN CASE WHEN in_value < 0 THEN RTRIM( 'minus ' || p_words )
WHEN in_value = 0 THEN 'Zero'
ELSE RTRIM( p_words ) END;
END;
/
查询:
SELECT TO_WORDS( 999999999999998 ) FROM DUAL;
输出:
TO_WORDS(999999999999998)
--------------------------------------------------------------------------------
nine hundred ninety-nine trillion nine hundred ninety-nine billion nine hundred
ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-eight
更新 - 仅使用 SQL
(注意,这并不简单)
SQL Fiddle
Oracle 11g R2 架构设置:
CREATE TABLE NUMBERS ( value ) AS
SELECT 123456789012345678901 FROM DUAL UNION ALL
SELECT 999000 FROM DUAL;
查询 1:
WITH words ( id, word ) AS (
SELECT 0, null FROM DUAL UNION ALL
SELECT 1, ' thousand' FROM DUAL UNION ALL
SELECT 2, ' million' FROM DUAL UNION ALL
SELECT 3, ' billion' FROM DUAL UNION ALL
SELECT 4, ' trillion' FROM DUAL UNION ALL
SELECT 5, ' quadrillion' FROM DUAL UNION ALL
SELECT 6, ' quintillion' FROM DUAL UNION ALL
SELECT 7, ' sextillion' FROM DUAL UNION ALL
SELECT 8, ' septillion' FROM DUAL UNION ALL
SELECT 9, ' octillion' FROM DUAL UNION ALL
SELECT 10, ' decillion' FROM DUAL UNION ALL
SELECT 11, ' undecillion' FROM DUAL UNION ALL
SELECT 12, ' duodecillion' FROM DUAL
)
SELECT MAX( n.value ) AS value,
LISTAGG(
CASE
WHEN MOD( FLOOR( n.value / POWER( 1000, t.column_value ) ), 1000 ) = 0
THEN NULL
ELSE TO_CHAR(
TO_DATE(
MOD( FLOOR( n.value / POWER( 1000, t.column_value ) ), 1000 ),
'j'
),
'jsp'
) || w.word
END,
' '
) WITHIN GROUP ( ORDER BY t.COLUMN_VALUE DESC )
AS to_words
FROM numbers n,
TABLE(
CAST(
MULTISET(
SELECT LEVEL - 1
FROM DUAL
CONNECT BY POWER( 1000, LEVEL - 1 ) < n.value
) AS SYS.ODCINUMBERLIST
)
) t,
words w
WHERE t.column_value = w.id
GROUP BY n.ROWID
Results:
| VALUE | TO_WORDS |
|-----------------------|--------------------------------------|
| 12345678901234568901 | one hundred twenty-three quintillion |
| | four hundred fifty-six quadrillion |
| | seven hundred eighty-nine trillion |
| | twelve billion |
| | three hundred forty-five million |
| | six hundred seventy-eight thousand |
| | nine hundred one |
| | |
| 999000 | nine hundred ninety-nine thousand |
【讨论】:
太好了,但不幸的是我正在开发 Oracle Fusion BI 发布者,显然我无法使用任何程序,只能使用普通的选择查询 它不是一个过程 - 它是一个可以在 SQL 查询中使用的函数。如果您的 DBA 可以创建该函数,那么您也许可以使用它。 很遗憾他不能,我们没有这样的特权:/ @user2219385 使用纯 SQL 查询进行了更新,以执行与函数完全相同的操作...但并不简单。以上是关于如果超过 78 个字符,则使用 TIMESTAMP 的 TO_CHAR 会引发 ORA-01877的主要内容,如果未能解决你的问题,请参考以下文章
如何发布超过 280 个字符的推文?因此,如果字符串 > 280,则打印前 280 个字符,然后在该线程中再次使用其余字符发送推文
AngularJS:如何仅在字符串超过限制时才使用limitTo过滤器显示省略号
在2005年,Unicode 的第十万个字符被采纳且认可成为标准之一(超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现)