这会将十进制数字转换为罗马数字:
select to_char(515, 'RN') from dual;
返回:DXV
如何做相反的事情?这引发了ORA-01722: Invalid number
:
select to_number('DXV', 'RN') from dual;
答案 0 :(得分:2)
只是为了好玩,另一种方法是将字符串分成单个和相邻数字组,允许使用标准减法表示法(谢谢Wikipedia;将每个单个或相邻对或数字转换为它们的十进制等效项;然后对它们求和:
with t (str) as (select 'MCMLXXXIV' from dual)
select sum(
case regexp_substr(str, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, level)
when 'M' then 1000
when 'CM' then 900
when 'D' then 500
when 'CD' then 400
when 'C' then 100
when 'XC' then 90
when 'L' then 50
when 'XL' then 40
when 'X' then 10
when 'IX' then 9
when 'V' then 5
when 'IV' then 4
when 'I' then 1
end) as decimals
from t
connect by regexp_substr(str, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, level) is not null;
DECIMALS
----------
1984
请注意,正则表达式中搜索词的顺序与其等效的十进制顺序不同;您需要将M之前的CM匹配为减号。
如果这是您需要做的事情,那么可能值得创建一个确定性函数(当然,对于递归CTE方法也是如此)。在这种情况下,您可以切换到PL / SQL循环以减少上下文切换:
create or replace function roman_to_decimal(p_roman varchar2)
return number deterministic is
l_decimal number := 0;
begin
for i in 1..regexp_count(p_roman, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)') loop
l_decimal := l_decimal +
case regexp_substr(p_roman, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, i)
when 'M' then 1000
when 'CM' then 900
when 'D' then 500
when 'CD' then 400
when 'C' then 100
when 'XC' then 90
when 'L' then 50
when 'XL' then 40
when 'X' then 10
when 'IX' then 9
when 'V' then 5
when 'IV' then 4
when 'I' then 1
end;
end loop;
return l_decimal;
end;
/
select roman_to_decimal('DXV'), roman_to_decimal('MCMLXXXIV')
from dual;
ROMAN_TO_DECIMAL('DXV') ROMAN_TO_DECIMAL('MCMLXXXIV')
----------------------- -----------------------------
515 1984
您可以使用以下方法查看和检查所有转化(1-3 {是to_char()
支持的范围)
with t (orig, roman) as (
select level, to_char(level, 'RN') from dual connect by level < 4000
)
select orig, roman, roman_to_decimal(roman)
from t;
ORIG ROMAN ROMAN_TO_DECIMAL(ROMAN)
---------- --------------- -----------------------
1 I 1
2 II 2
3 III 3
4 IV 4
5 V 5
6 VI 6
7 VII 7
8 VIII 8
9 IX 9
10 X 10
11 XI 11
...
3994 MMMCMXCIV 3994
3995 MMMCMXCV 3995
3996 MMMCMXCVI 3996
3997 MMMCMXCVII 3997
3998 MMMCMXCVIII 3998
3999 MMMCMXCIX 3999
或者只是为了验证它们都转换回了原始值:
with t (original, roman) as (
select level, to_char(level, 'RN') from dual connect by level < 4000
)
select original, roman, roman_to_decimal(roman)
from t
where roman_to_decimal(roman) != original;
no rows selected
对于我来说,这比递归CTE慢得多,但在其他版本和平台上可能有所不同;就像我说的,只是为了好玩...
答案 1 :(得分:1)
此代码是从LiveSQL站点(https://livesql.oracle.com/apex/livesql/file/content_CESOH7H2D4O88XLW60330Q3L9.html)复制的。作者是Natalka Roshak女士。
使用递归子查询将罗马数字转换为十进制格式。将罗马数字放入With子句中的ROMAN内联表中,以传递要转换的罗马数字。
SQL> WITH
2 roman ( numeral ) AS
3 ( SELECT 'MCMLXXXVII' AS numeral FROM dual),
4 romtodec (
5 thisval,
6 thisone,
7 thisdec,
8 lastdec,
9 remaining,
10 pos
11 ) AS ( SELECT 0 AS thisval,
12 CAST(NULL AS VARCHAR2(4000) ) AS thisone,
13 0 AS thisdec,
14 0 AS lastdec,
15 roman.numeral AS remaining,
16 length(roman.numeral) AS pos
17 FROM roman
18 UNION ALL
19 SELECT
20 CASE
21 WHEN romtodec.thisdec >= romtodec.lastdec THEN romtodec.thisval + thisdec
22 ELSE romtodec.thisval - thisdec
23 END
24 AS thisval,
25 substr(romtodec.remaining,length(romtodec.remaining),1) AS thisone,
26 CASE substr(romtodec.remaining,length(romtodec.remaining),1)
27 WHEN 'M' THEN 1000
28 WHEN 'D' THEN 500
29 WHEN 'C' THEN 100
30 WHEN 'L' THEN 50
31 WHEN 'X' THEN 10
32 WHEN 'V' THEN 5
33 WHEN 'I' THEN 1
34 ELSE 0
35 END
36 AS thisdec,
37 romtodec.thisdec AS lastdec,
38 substr(romtodec.remaining,1,length(romtodec.remaining) - 1) AS remaining,
39 length(romtodec.remaining) - 1 AS pos
40 FROM romtodec
41 WHERE pos >= 0
42 ) SELECT thisval
43 FROM romtodec
44 WHERE pos IS NULL;
THISVAL
----------
1987
SQL>
答案 2 :(得分:0)
请尝试在下面的查询中查询罗马数字到十进制数字:
链接-https://livesql.oracle.com/apex/livesql/file/content_CESOH7H2D4O88XLW60330Q3L9.html
WITH Roman (Numeral) AS
(select 'DXV' as Numeral from dual),
RomToDec (ThisVal, ThisOne, ThisDec, LastDec, Remaining, Pos) AS
(SELECT 0 as ThisVal, cast(null as varchar2(4000)) as ThisOne,
0 as ThisDec, 0 as LastDec,
Roman.Numeral as Remaining , length(Roman.Numeral) as Pos
FROM Roman
UNION ALL
SELECT
case
when RomToDec.ThisDec >= RomToDec.LastDec then RomToDec.ThisVal + ThisDec
else RomToDec.ThisVal-ThisDec
END as ThisVal,
substr(RomToDec.Remaining,length(RomToDec.Remaining),1) as ThisOne,
case substr(RomToDec.Remaining,length(RomToDec.Remaining),1)
when 'M' then 1000
when 'D' then 500
when 'C' then 100
when 'L' then 50
when 'X' then 10
when 'V' then 5
when 'I' then 1
else 0 END as ThisDec,
RomToDec.ThisDec as LastDec,
substr(RomToDec.Remaining,1,length(RomToDec.Remaining)-1) as Remaining,
length(RomToDec.Remaining)-1 as Pos
from RomToDec
where Pos >= 0
)
select thisVal
from RomToDec
where pos is null
答案 3 :(得分:-3)
我认为您正在寻找TO_NUMBER
函数。
类似... to_number('XIV', 'RN') ...