做手机号脱敏,有人写了 translate(phone, 0123456789, 十个星号),想把中间四位变星号,结果整串数字全成了星号。他不理解,为什么只替换了一部分,输出却整列都变了。

translate 不是替换子串,它是逐字符对位替换。语法是 translate(expr, from_str, to_str),它把 expr 里出现的每个字符,在 from_str 里找到位置,换成 to_str 同位置的字符。from_str 比 to_str 长的时候,多出来的那些字符会被直接删掉,不是保留原样。这就是最多人踩的坑。

三个参数的实际行为

  • 等长:from 和 to 字符数一样,就是一对一映射,比如 translate(col, abc, xyz)
  • from 更长:多出的字符被删掉,常用它来清掉换行、空格、特殊符号
  • to 为空:相当于把所有匹配字符删干净,translate(col, chr(10), 空串) 就是去换行
  • expr 为 null:结果直接是 null,不报错,统计时容易漏数据

所以要把手机号中间四位变星号,正确做法是 substr 拼字符串,或者用 regexp_replace。translate 适合字符级清洗,不适合位置替换,硬套只会得到一堆星号。

select substr(phone,1,3) || '****' || substr(phone,8,4) from users;

和 replace 别搞混

replace 是按字符串整体匹配替换,translate 是按单个字符。想删掉所有括号和横线,用 translate 一行就够;想把某个单词换掉,得用 replace。两个函数混着用,逻辑会变得没人看得懂。

中文在 Oracle 里受字符集影响,AL32UTF8 下一个汉字占三字节,但 translate 的 from 和 to 参数是按字符算的,不用自己拆字节。如果库是 ZHS16GBK,某些生僻字可能出问题,测试时用真实数据跑一遍更稳,别拿几个常用字试完就上线。

还有个细节,from 里重复的字符以第一次出现为准,to 里对应位置之后的内容会被忽略。写清洗规则时别把同一个字符写两遍,否则结果和预期对不上。

选型上没什么好纠结的:字符清洗用 translate,字符串替换用 replace,位置处理老实用 substr。想用一个函数干完所有事,最后基本都是返工。