Oracle把阿拉伯数字转换为汉字

在 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 结果:

结果合理。

相关推荐
欢呼的太阳6 小时前
数据库设计Step by Step (10)——范式化
服务器·数据库·oracle
喜欢的名字被抢了7 小时前
MySQL基础入门:从零理解数据库与SQL
数据库·mysql·教程
Elastic 中国社区官方博客8 小时前
如何比较两个 Elasticsearch 索引并找出缺失的文档
大数据·运维·数据库·elasticsearch·搜索引擎
DLYSB_9 小时前
生命通道:如何用 HIS 医疗系统直连网络声光终端,打造“零延误”的危急值响应网关?
java·网络·数据库·报警灯
儒雅的大叔9 小时前
MySQL数据库InnoDB数据恢复工具使用总结
数据库·mysql·adb
观远数据9 小时前
ChatBI选型对比:从意图识别到SQL修复,六个维度打分决定是否值得投产
数据库·人工智能·sql
嘉&年华9 小时前
【第008篇】通过dexp和dimp命令导出和导入dmp文件(适用于达梦数据库)
数据库·sql
哥是强混11 小时前
Means:基于 .NET 10 打造的开源自部署 S3 兼容对象存储服务
网络·数据库·.net
ffqws_11 小时前
redis面试:如何保证双写一致
数据库·redis·缓存
水无痕simon11 小时前
6 数据库同义词
数据库