在 Oracle 中创建函数
sql
CREATE OR REPLACE FUNCTION F_NUM_TO_CHN(p_num IN NUMBER) RETURN VARCHAR2 IS
v_num VARCHAR2(50);
v_len NUMBER;
v_res VARCHAR2(4000) := '';
v_digit NUMBER;
v_char VARCHAR2(4);
v_unit VARCHAR2(4);
v_is_zero BOOLEAN;
-- 【核心修复】v_need_zero 标记位:记录上一位是否是零,且尚未输出"零"
v_need_zero BOOLEAN := FALSE;
v_chars CONSTANT VARCHAR2(20) := '零一二三四五六七八九';
BEGIN
IF p_num IS NULL THEN RETURN NULL; END IF;
IF p_num = 0 THEN RETURN '零'; END IF;
IF p_num < 0 THEN RETURN '负' || F_NUM_TO_CHN(ABS(p_num)); END IF;
v_num := TO_CHAR(p_num);
v_len := LENGTH(v_num);
FOR i IN 1 .. v_len LOOP
v_digit := TO_NUMBER(SUBSTR(v_num, i, 1));
v_is_zero := (v_digit = 0);
-- 1. 确定当前位的单位
CASE (v_len - i)
WHEN 0 THEN v_unit := '';
WHEN 1 THEN v_unit := '十';
WHEN 2 THEN v_unit := '百';
WHEN 3 THEN v_unit := '千';
WHEN 4 THEN v_unit := '万';
WHEN 5 THEN v_unit := '十';
WHEN 6 THEN v_unit := '百';
WHEN 7 THEN v_unit := '千';
WHEN 8 THEN v_unit := '亿';
WHEN 9 THEN v_unit := '十';
WHEN 10 THEN v_unit := '百';
WHEN 11 THEN v_unit := '千';
WHEN 12 THEN v_unit := '万亿';
ELSE v_unit := '';
END CASE;
IF v_is_zero THEN
-- 当前位是 0
-- 标记需要补零(但先不补,等到遇到下一个非零数字再补)
v_need_zero := TRUE;
ELSE
-- 当前位是非零数字
-- 【核心逻辑】检查是否需要补"零"
IF v_need_zero THEN
-- 特殊情况处理:如果是"十"位,且前面全是0(即数字是 10, 100010 这种结构的十位)
-- 规则:10->十 (不补零,也不读一); 101->一百零一 (补零)
-- 判断条件:如果单位是"十",且这是该节的第一个非零数(即前一位是0,且再前一位也是0或不存在)
-- 简单判断:如果结果是空,或者结果末尾是"万"/"亿",且单位是"十",且数字是1 -> 不补零,不读一
v_char := SUBSTR(v_chars, v_digit + 1, 1);
-- 特殊场景:10-19 的开头 (如 10, 11, 1010)
-- 如果单位是"十",且数字是"一",且 (前面没有数字 或者 前面刚结束了"万/亿"节)
-- 此时不需要补"零",也不需要读"一",直接读"十"
IF v_unit = '十' AND v_digit = 1 THEN
IF v_res = '' OR SUBSTR(v_res, -1) IN ('万', '亿', '零') THEN
-- 情况 A: 开头是 1 (如 12 -> 十二) -> 不补零,不加"一",只加"十"
-- 情况 B: 万/亿后是 1 (如 10010 -> 一万零十? 不对,是一万零一十? 中文习惯是一万零一十)
-- 修正:10010 -> 一万零一十。只有当它是整个数的最高位时(10-19),才省略"一"。
IF v_res = '' THEN
-- 整个数的最高位是十位且为1 (10-19) -> 省略"一",且不补零
v_res := v_res || v_unit;
v_need_zero := FALSE; -- 消耗掉零标记(其实前面也没零)
CONTINUE; -- 跳过后续添加逻辑
ELSIF SUBSTR(v_res, -1) = '零' THEN
-- 前面已经补过零了 (如 101 -> 一百零...),这里直接加"十"
v_res := v_res || v_unit;
v_need_zero := FALSE;
CONTINUE;
ELSIF SUBSTR(v_res, -1) IN ('万', '亿') THEN
-- 万/亿后面紧跟十位 (如 10010 -> 一万... 这里的逻辑稍微复杂)
-- 10010: 1(万) 0 0 1(十) 0 -> 一万零一十
-- 10000: 一万
-- 10012: 一万零一十二
-- 如果前面是万/亿,且中间有0 (v_need_zero为真),则必须先补零
v_res := v_res || '零' || v_char || v_unit;
v_need_zero := FALSE;
CONTINUE;
END IF;
END IF;
END IF;
-- 通用情况:补"零"
v_res := v_res || '零';
v_need_zero := FALSE; -- 标记已消费
END IF;
-- 添加当前数字和单位
v_char := SUBSTR(v_chars, v_digit + 1, 1);
-- 再次处理 10-19 省略"一"的情况 (针对非零补位后的情况)
-- 如果单位是"十",数字是"一",且它是该节的最高位(即前面没有百/千,或者前面是万/亿且没补零?)
-- 简化规则:如果单位是"十",数字是"一",且 (结果是空 或者 结果末尾是"零"/"万"/"亿" 且 刚刚没补零?)
-- 最稳妥的省略规则:仅当"一十"出现在字符串的最开头时省略。其他情况(如一百一十、一万零一十)通常保留"一"或根据口语习惯。
-- 标准普通话:110 -> 一百一十 (省略一); 1010 -> 一千零一十 (省略一? 不,是一千零一十).
-- 实际上:11-19 在十位上通常省略"一"。
IF v_unit = '十' AND v_digit = 1 THEN
-- 如果前面是空,或者前面是"零" (如 101 -> 一百零一十? 不,1010->一千零一十)
-- 让我们看标准:
-- 10: 十
-- 11: 十一
-- 110: 一百一十 (省略)
-- 1010: 一千零一十 (省略)
-- 结论:只要单位是十,数字是一,通常都省略"一",除非为了强调。
-- 但要注意:如果前面刚补了"零",变成 "零一十" 就不对了,应该是 "零十"? 不对,是 "零一十"。
-- 等等,1010 读作 "一千零一十"。这里 "一" 是保留的!
-- 只有当 "一十" 位于 **该节的最前端** 且 **没有前导零** 时才省略?
-- 重新校准:
-- 10 -> 十 (省略)
-- 110 -> 一百一十 (省略)
-- 1010 -> 一千零一十 (保留 "一") -> 为什么?因为中间隔了零。
-- 1100 -> 一千一百 (省略)
-- 修正逻辑:如果前面紧挨着的是 "零",则保留 "一"。如果前面是其他单位或开头,则省略。
IF v_res = '' OR SUBSTR(v_res, -1) NOT IN ('零') THEN
v_res := v_res || v_unit; -- 省略 "一"
ELSE
v_res := v_res || v_char || v_unit; -- 保留 "一"
END IF;
ELSE
v_res := v_res || v_char || v_unit;
END IF;
END IF;
END LOOP;
-- 清理末尾可能多余的零 (理论上循环逻辑不会产生,但以防万一)
-- 如果结果是 "零" (输入0的情况已在开头处理),这里不需要动
RETURN v_res;
END;
Sql 测试:
sql
SELECT
num_val,
F_NUM_TO_CHN(num_val) as result
FROM (
SELECT 1 AS num_val FROM DUAL UNION ALL
SELECT 10 FROM DUAL UNION ALL
SELECT 11 FROM DUAL UNION ALL
SELECT 100 FROM DUAL UNION ALL
SELECT 101 FROM DUAL UNION ALL
SELECT 1000 FROM DUAL UNION ALL
SELECT 1001 FROM DUAL UNION ALL -- 重点测试:应为一千零一
SELECT 1010 FROM DUAL UNION ALL -- 重点测试:应为一千零一十
SELECT 1011 FROM DUAL UNION ALL
SELECT 1100 FROM DUAL UNION ALL
SELECT 10000 FROM DUAL UNION ALL
SELECT 10001 FROM DUAL UNION ALL -- 重点测试:应为一万零一
SELECT 10010 FROM DUAL UNION ALL -- 重点测试:应为一万零一十
SELECT 12345 FROM DUAL
);
Sql 结果:

结果合理。