从 Oracle Varchar2 中查找和删除非 ASCII 字符

Posted

技术标签:

【中文标题】从 Oracle Varchar2 中查找和删除非 ASCII 字符【英文标题】:Finding and removing Non-ASCII characters from an Oracle Varchar2 【发布时间】:2011-01-15 05:47:10 【问题描述】:

我们目前正在将我们的一个 oracle 数据库迁移到 UTF8,并且我们发现了一些接近 4000 字节 varchar 限制的记录。 当我们尝试迁移这些记录时,它们会失败,因为它们包含变成多字节 UF8 字符的字符。 我想在 PL/SQL 中做的是找到这些字符以查看它们是什么,然后更改它们或删除它们。

我想做:

SELECT REGEXP_REPLACE(COLUMN,'[^[:ascii:]],'')

但是 Oracle 没有实现 [:ascii:] 字符类。

有没有一种简单的方法可以做我想做的事?

【问题讨论】:

您可能希望将 ç 替换为 c 等等。扔掉整个字符比丢弃变音符号更糟糕。 我们首先需要弄清楚角色是什么,然后再决定如何处理它们。 【参考方案1】:

在单字节 ASCII 兼容编码(例如 Latin-1)中,ASCII 字符只是 0 到 127 范围内的字节。因此您可以使用 [\x80-\xFF] 之类的东西来检测非 ASCII 字符。

【讨论】:

我尝试按照建议使用十六进制代码:- regexp_replace(column,'[\x00-\xFF]','') 不删除任何大写字母 - 我是否有转义或还有什么我需要做的吗? 我在使用您的解决方案时遇到了问题。这个答案已被接受,所以我相信这不是完全错误的,而是 1.) oracle 不支持正则表达式语法以通过其十六进制表示指定代码点/字符(即'\x80');相反,您必须自己指定字符(但是,正则表达式模式是一个字符串表达式,因此您可以使用类似 '['||chr(128)||'-'||chr(255)||']' 的东西),2.)尝试替换 '['||chr(32)||'-'||chr(127)||']' 中的所有字符会导致 ora-12728 错误(无效正则表达式中的范围)。我的数据库字符集是 al32utf8。有什么想法吗? 我应该补充一点 1.) 数据库是 oracle 11.2.0.3.0, 2.) 范围 32-122、32-255 不会导致错误,但 3.) 应用于字符串由大小写混合的字母和数字组成的行为与您的预期相反(即REGEXP_REPLACE ( 'abc', '['||chr(32)||'-'||chr(128)||']' , '_' ) 产生abc,而REGEXP_REPLACE ( 'abc', '[^'||chr(32)||'-'||chr(128)||']' , '_' ) 返回___)。【参考方案2】:

使用正则表达式可能有更直接的方法。运气好的话,别人会提供它。但这是我无需查阅手册即可完成的操作。

创建一个 PLSQL 函数来接收您的输入字符串并返回一个 varchar2。

在 PLSQL 函数中,对您的输入执行 asciistr()。 PLSQL 是因为它可能返回一个长度超过 4000 的字符串,并且 PLSQL 中有 32K 可用于 varchar2。

该函数将非 ASCII 字符转换为 \xxxx 表示法。因此,您可以使用正则表达式来查找和删除它们。然后返回结果。

【讨论】:

【参考方案3】:

我遇到了类似的问题,并在博客上写了 here。 我从字母数字的正则表达式开始,然后添加了一些我喜欢的基本标点符号:

select dump(a,1016), a, b
from
 (select regexp_replace(COLUMN,'[[:alnum:]/''%()> -.:=;[]','') a,
         COLUMN b
  from TABLE)
where a is not null
order by a;

我使用带有 1016 变体的转储来给出我想要替换的十六进制字符,然后我可以在 utl_raw.cast_to_varchar2 中使用。

【讨论】:

【参考方案4】:

选择可能类似于以下示例:

select nvalue from table
where length(asciistr(nvalue))!=length(nvalue)  
order by nvalue;

【讨论】:

好主意,但这样您实际上是在识别具有数据的字段,其中字节大小与它们所代表的符号数量不同。 也错误地将“\”键返回为非 ascii 字符。【参考方案5】:

如果您使用ASCIISTR 函数将Unicode 转换为\nnnn 形式的文字,那么您可以使用REGEXP_REPLACE 去除这些文字,就像这样...

UPDATE table SET field = REGEXP_REPLACE(ASCIISTR(field), '\\[[:xdigit:]]4', '')

...其中 field 和 table 分别是您的字段和表名。

【讨论】:

如果字符串的长度接近 4000,那么ASCIISTR() 会将字符串扩展到超出此限制,并且字符串将被截断为 4000 个字符(从末尾丢失多余的字符)。 SQLFIDDLE 这也会拾取反斜杠字符,因为它是 ascii,所以这是不可取的 要围绕这个编码,where replace(asciistr(field),asciistr('\'),'\') <> field【参考方案6】:

我在这里找到了答案:

http://www.squaredba.com/remove-non-ascii-characters-from-a-column-255.html

CREATE OR REPLACE FUNCTION O1DW.RECTIFY_NON_ASCII(INPUT_STR IN VARCHAR2)
RETURN VARCHAR2
IS
str VARCHAR2(2000);
act number :=0;
cnt number :=0;
askey number :=0;
OUTPUT_STR VARCHAR2(2000);
begin
str:=’^'||TO_CHAR(INPUT_STR)||’^';
cnt:=length(str);
for i in 1 .. cnt loop
askey :=0;
select ascii(substr(str,i,1)) into askey
from dual;
if askey < 32 or askey >=127 then
str :=’^'||REPLACE(str, CHR(askey),”);
end if;
end loop;
OUTPUT_STR := trim(ltrim(rtrim(trim(str),’^'),’^'));
RETURN (OUTPUT_STR);
end;
/

然后运行这个来更新你的数据

update o1dw.rate_ipselect_p_20110505
set NCANI = RECTIFY_NON_ASCII(NCANI);

【讨论】:

【参考方案7】:

以下也有效:

select dump(a,1016), a from (
SELECT REGEXP_REPLACE (
          CONVERT (
             '3735844533120%$03  ',
             'US7ASCII',
             'WE8ISO8859P1'),
          '[^!@/\.,;:<>#$%&()_=[:alnum:][:blank:]]') a
  FROM DUAL);

【讨论】:

【参考方案8】:

尝试以下方法:

-- To detect
select 1 from dual
where regexp_like(trim('xx test text æ¸¬è© ¦ “xmx” number²'),'['||chr(128)||'-'||chr(255)||']','in')

-- To strip out
select regexp_replace(trim('xx test text æ¸¬è© ¦ “xmxmx” number²'),'['||chr(128)||'-'||chr(255)||']','',1,0,'in')
from dual

【讨论】:

【参考方案9】:

我不建议将它用于生产代码,但它很有意义并且似乎可以工作:

SELECT REGEXP_REPLACE(COLUMN,'[^' || CHR(1) || '-' || CHR(127) || '],'')

【讨论】:

请注意,您通常应该从 32 而不是 1 开始,因为这是第一个可打印的 ascii 字符。其余的是控制字符,这在文本列中会很奇怪(甚至比我说的 >127 更奇怪)。但是从技术上讲,答案是正确的,这将检测到非 ascii 字符,给定原始的 7 位 ascii 标准。【参考方案10】:

Francisco Hayoz 给出的答案是最好的。如果 sql 可以为您做到,请不要使用 pl/sql 函数。

这是 Oracle 11.2.03 中的简单测试

select s
     , regexp_replace(s,'[^'||chr(1)||'-'||chr(127)||']','') "rep ^1-127"
     , dump(regexp_replace(s,'['||chr(127)||'-'||chr(225)||']','')) "rep 127-255"
from (
select listagg(c, '') within group (order by c) s
  from (select 127+level l,chr(127+level) c from dual connect by level < 129))

而“rep 127-255”是

Typ=1 Len=30: 226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,p>253,

即由于某种原因,此版本的 Oracle 不会替换 char(226) 及更高版本。 使用 '['||chr(127)||'-'||chr(225)||']' 可以得到想要的结果。 如果您需要替换其他字符,只需将它们添加到上面的正则表达式中,或者如果替换不同,则使用嵌套的 replace|regexp_replace 然后 ''(空字符串)。

【讨论】:

【参考方案11】:

谢谢,这对我有用。顺便说一句,上面的示例中缺少单引号。

REGEXP_REPLACE (COLUMN,'[^' || CHR (32) || '-' || CHR (127) || ']', ' '))

我在自动换行功能中使用了它。有时,传入的文本中嵌入的 NewLine/NL/CHR(10)/0A 会造成混乱。

【讨论】:

【参考方案12】:

这样做,它会工作。

trim(replace(ntwk_slctor_key_txt, chr(0), ''))

【讨论】:

欢迎来到 Stack Overflow!这个答案出现在低质量的审查队列中,大概是因为你没有解释代码。如果你确实解释了(在你的回答中),你更有可能获得更多的支持——提问者更有可能学到一些东西!【参考方案13】:

请注意,无论何时使用

regexp_like(column, '[A-Z]')

Oracle 的正则表达式引擎也会匹配来自 Latin-1 范围的某些字符:这适用于所有看起来类似于 ASCII 字符的字符,例如 Ä->A、Ö->O、Ü->U 等,所以[AZ] 不是您从其他环境(例如 Perl)中所知道的。

与其摆弄正则表达式,不如尝试在字符集升级之前更改 NVARCHAR2 数据类型。

另一种方法:如果您的数据库仅包含欧洲字符(即 Latin-1)字符,您可以尝试使用 SOUNDEX 函数,而不是删除部分字段内容。或者您只需编写一个函数,将 Latin-1 范围内的字符转换为外观相似的 ASCII 字符,例如

å => 一个 ä => 一个 ö => o

当然仅适用于转换为 UTF-8 时超过 4000 字节的文本块。

【讨论】:

【参考方案14】:

我回答这个问题有点晚了,但最近遇到了同样的问题(人们将各种东西剪切并粘贴到一个字符串中,我们并不总是知道它是什么)。 下面是一个简单的字符白名单方法:

SELECT est.clients_ref
  ,TRANSLATE (
              est.clients_ref
             ,   'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890#$%^&*()_+-=|[]:";<>?,./'
              || REPLACE (
                          TRANSLATE (
                                     est.clients_ref
                                    ,'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890#$%^&*()_+-=|[]:";<>?,./'
                                    ,'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
                                    )
                         ,'~'
                         )
             ,'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890#$%^&*()_+-=|[]:";<>?,./'
             )
      clean_ref

FROM edms_staging_table est

【讨论】:

【参考方案15】:

我认为这可以解决问题:

SELECT REGEXP_REPLACE(COLUMN, '[^[:print:]]', '')

【讨论】:

这很整洁,效果很好。作为附录,您还可以使用 REGEXP_REPLACE(Column,'[^ -~]','') 而不是上面提到的所有 Chr() 函数和字符串连接。 @Ciaran: REGEXP_REPLACE(Column,'[^ -~]','') 非常好,因为 Oracle 不支持 '[\x80-\xFF]'。应该在答案中。 如果您想保留新行,请使用regexp_replace(column, '[^ -~|[:space:]]', '') 它指定一个ascii字符范围,即空格(字符32)-(到)tilda“~”(字符126)asciitable.com 这是一个好的开始,但是“打印”类中有很多字符没有找到/删除。这绝对让我走上了正确的道路,所以谢谢你添加这个!【参考方案16】:

您可以尝试以下方法来搜索包含非 ascii 字符的列:

select * from your_table where your_col <> asciistr(your_col);

【讨论】:

【参考方案17】:

我有类似的要求(为了避免这种丑陋的 ORA-31061: XDB error: special char to escaped char conversion failed. ),但必须保留换行符。

我从一个很好的评论中尝试了这个

'[^ -~|[:space:]]'

但是得到了这个ORA-12728: invalid range in regular expression。

但它引导我找到我的解决方案:

select t.*, regexp_replace(deta, '[^[:print:]|[:space:]]', '#') from  
    (select '-   <- strangest thing here, and I want to keep line break after
-' deta from dual ) t

显示(在我的 TOAD 工具中)为

替换所有不在集合中的^ =>(打印[:print:] 或空格|[:space:] 字符)

【讨论】:

【参考方案18】:

如this comment 和this comment 中所述,您可以使用范围。 使用 Oracle 11,以下工作非常好:

SELECT REGEXP_REPLACE(dummy, '[^ -~|[:space:]]', '?') AS dummy FROM DUAL;

这会将可打印范围之外的任何内容替换为问号。

这将按原样运行,因此您可以通过安装验证语法。 将dummydual 替换为您自己的列/表。

【讨论】:

以上是关于从 Oracle Varchar2 中查找和删除非 ASCII 字符的主要内容,如果未能解决你的问题,请参考以下文章

无效的 XML 字符错误 - 如何从 VARCHAR2 数据库列中查找无效字符?

Oracle 12c 扩展为支持 varchar2 > 4000 字节不适用于非 sysdba 用户

hibernate+oracle+主键varchar2类型,增加序列策略注解失败

oracle数据库之如何将blob类型转换为varchar2

oracle varchar2 为啥改不成clob

Oracle:添加或删除表语句