Oracle 的 CHR 函数经常被简化成“把数字转成字符”的工具,但真正用到动态 SQL、报表排版或数据清洗时,很多人会发现它牵涉到字符集、控制字符和不可见字符的细节。本文先把基础用法和参数规则讲清楚,再结合几个容易踩坑的场景说明如何避开常见问题。

一、基础语法与返回规则
函数名不区分大小写,通常写作 CHR(n),其中 n 是数值类型参数。它返回数据库字符集里与这个数值对应的字符。最典型的例子是 CHR(65) 返回字母 A,CHR(10) 返回换行符,CHR(13) 返回回车符。可以使用下面的语句一次查看三个结果:
SELECT CHR(65) AS letter,
CHR(10) AS line_feed,
CHR(13) AS carriage_return
FROM dual;
虽然查询界面里换行符和回车符看起来就像空白,但它们真实存在于返回值中。可以用 LENGTH 函数观察字符串长度变化,也可以把结果导出到文本文件后查看排版效果。
参数 n 理论上接受 NUMBER 类型,也可以由其他类型隐式转换。实际使用中建议始终传入整数,避免小数或非数值造成隐式转换结果不确定。对于单字节数据库字符集,CHR 的常用范围是 0 到 255,其中 0 到 127 基本与 ASCII 表一致。超出这个范围时,Oracle 的处理方式会依赖具体字符集,结果不如常用区间稳定,因此不要依赖那些边界行为。
CHR 与 ASCII 函数互为逆运算。下面语句可以验证这个关系:
SELECT ASCII('A') AS code_value,
CHR(ASCII('A')) AS char_value
FROM dual;
这一步在排查不可见字符时非常有用,因为可以先通过 ASCII 取出某个位置的字符编码,再用 CHR 反向生成该字符进行比对或清理。
二、操作要点:生成控制字符与特殊符号
处理报表和导出文本时,最常用的组合是制表符、回车和换行。CHR(9) 表示制表符,CHR(13) 表示回车,CHR(10) 表示换行。Windows 下的换行通常建议写成 CHR(13) || CHR(10),也就是 CRLF,而 Unix/Linux 场景可以只使用 CHR(10)。下面的语句演示如何把字段拼成一份带制表符分隔的文本行:
SELECT '姓名' || CHR(9) || '部门' || CHR(13) || CHR(10) ||
'张三' || CHR(9) || '技术部' AS report_line
FROM dual;
这种输出在报表导出、接口报文拼接和日志格式化时很实用,可以让文本在 Excel 或文本编辑器中直接呈现正确的分列和换行结构。
另一个常见需求是在动态 SQL 中生成单引号。字符串里出现单引号时,直接用 CHR(39) 会让拼接逻辑更清晰。比如要查询 ename 等于 SMITH 的记录,可以这样写:
DECLARE v_sql VARCHAR2(200); v_cnt NUMBER; BEGIN v_sql := 'SELECT COUNT(*) FROM emp WHERE ename = ' || CHR(39) || 'SMITH' || CHR(39); EXECUTE IMMEDIATE v_sql INTO v_cnt; DBMS_OUTPUT.PUT_LINE(v_cnt); END; /
虽然也可以使用两个连续单引号来表示字符串内的单引号,但 CHR(39) 的方式在拼接变量较多时更好阅读,也便于统一维护特殊字符。
数据清洗场景中也经常需要移除换行、回车和制表符。下面语句通过嵌套 REPLACE 把三类常见控制字符替换为空字符串:
SELECT REPLACE(
REPLACE(
REPLACE(column_name, CHR(10), ''),
CHR(13), ''),
CHR(9), '') AS cleaned_text
FROM table_name;
这类清洗在导入外部文件或拼接多行文本时非常必要。否则这些不可见字符会干扰 WHERE 条件、GROUP BY 分组和前端展示,经常出现“看着一样却关联不上”的情况。
三、常见疑问与避坑指南
第一个容易踩坑的是 CHR(0)。它返回空字符,也称 NUL 字符。虽然逻辑上它可能只是字符串中的一个字符,但在很多 Oracle 客户端、日志工具和接口程序里,空字符会被当作字符串结束标记,导致后面的内容被截断或显示异常。不要在正常业务字段里插入 CHR(0),尤其是手机号、地址、备注等文本字段。如果怀疑数据里混入了空字符,可以用 INSTR(column_name, CHR(0)) 判断,也可以结合 DUMP 查看完整字节内容。
第二个问题是字符集影响。同一个 CHR(128) 在不同数据库字符集下可能返回完全不同的字符。数据库字符集可以通过 v$nls_parameters 查看:
SELECT value FROM v$nls_parameters WHERE parameter = 'NLS_CHARACTERSET';
如果数据库字符集是 AL32UTF8,CHR(20013) 通常可以返回汉字“中”;但在单字节字符集下传入 20013 这样的数值可能无法得到预期结果,甚至报错。换句话说,CHR 的行为绑定数据库字符集,不是固定不变的。
和 CHR 类似的是 NCHR。区别在于 NCHR 使用国家字符集,返回 NVARCHAR2 类型,更适合需要统一 Unicode 输出的场景。下面语句可以对比两者:
SELECT CHR(20013) AS db_char,
NCHR(20013) AS national_char
FROM dual;
如果应用需要跨环境稳定生成某个 Unicode 字符,优先考虑 NCHR,避免数据库字符集切换后 CHR 结果变化。
第三个常见疑问是如何定位不可见字符。数据看起来没问题,但 TRIM 去不掉、长度总是不对,多半是因为字段里含有非打印字符。此时不要凭肉眼判断,要用 DUMP 和 ASCII 看编码:
SELECT DUMP(column_name) AS dump_info,
ASCII(SUBSTR(column_name, 3, 1)) AS third_char_code
FROM table_name
WHERE id = 1;
如果第三个字符的编码是 10、13 或 9,就分别对应换行、回车和制表符。DUMP 会进一步显示每个字节的十六进制值,适合排查等号比较失败、关联不上的问题。
四、工程实践:少走弯路的几个建议
动态 SQL 中如果只是为了处理字符串里的单引号,完全可以使用绑定变量。绑定变量不仅避免大量 CHR(39) 拼接,还能降低 SQL 注入风险、提高执行计划复用率。写法如下:
DECLARE
v_cnt NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM emp WHERE ename = :ename'
INTO v_cnt
USING 'SMITH';
DBMS_OUTPUT.PUT_LINE(v_cnt);
END;
/
只有在确实需要生成控制字符、输出定长报文、拼接特殊分隔符时,才显式使用 CHR,这样代码意图更清楚。
当系统里多处使用换行符、制表符时,建议把这些值封装为包常量,而不是在 SQL 里到处写裸的 CHR(10)。例如:
CREATE OR REPLACE PACKAGE char_const AS c_lf CONSTANT VARCHAR2(1) := CHR(10); c_cr CONSTANT VARCHAR2(1) := CHR(13); c_tab CONSTANT VARCHAR2(1) := CHR(9); c_quote CONSTANT VARCHAR2(1) := CHR(39); END; /
调用时使用 char_const.c_lf 这样的命名常量,既方便统一修改,也能让同事一眼看懂当前插入的是哪种控制字符。
最后建议在关键业务表上建立不可见字符检查脚本。可以定期用 INSTR 检查 CHR(10)、CHR(13)、CHR(9) 和 CHR(0) 是否出现在文本字段中。发现异常后,再结合 REPLACE 清洗或追溯到上游导入任务。与其等到报表显示混乱、关联失败甚至接口传输截断后再查,不如把检查放到数据入库或定时任务里,处理成本会低很多。
Oracle Chr函数ASCII码转换SQL特殊字符处理修改时间:2026-10-04 11:37:32