11

由于遗留原因,我们在 Oracle 10 数据库中有一个 VARCHAR2 列(其中字符编码设置为),其中AL32UTF8包含一些非 UTF-8 值。这些值始终位于以下字符集中之一:

  • 美国ASCII
  • UTF-8
  • CP1252
  • 拉丁语-1

我编写了一个 Perl 函数来修复数据库外的损坏值。对于此数据库列中的值,它会遍历此编码列表并尝试将值转换为 UTF-8。如果转换失败,它会尝试下一个编码。第一个无误转换的是我们保留的值。现在,我想在数据库中复制此功能,以便任何人都可以使用它。

但是,我能找到的只是这个CONVERT函数,它永远不会失败,但会为它无法识别的字符插入一个替换字符。因此,据我所知,无法知道转换何时失败。

因此,我有两个问题:

  1. 是否有一些现有的接口试图将字符串转换为编码列表之一,返回第一个成功的?
  2. 如果没有,如果它不能将字符串转换为编码,是否有其他接口指示失败?如果是这样,那么我可以编写前面的函数。

更新:

作为参考,我在 PL/pgSQL 中编写了这个 PostgreSQL 函数,它完全符合我的需要:

CREATE OR REPLACE FUNCTION encoding_utf8(
    bytea
) RETURNS TEXT LANGUAGE PLPGSQL STRICT IMMUTABLE AS $$
DECLARE
    encoding TEXT;
BEGIN
    FOREACH encoding IN ARRAY ARRAY[
        'UTF8',
        'WIN1252',
        'LATIN1'
    ] LOOP
        BEGIN
            RETURN convert_from($1, encoding);
        EXCEPTION WHEN character_not_in_repertoire OR untranslatable_character THEN
            CONTINUE;
        END;
    END LOOP;
END;
$$;

我非常想知道如何在 Oracle 中做同样的事情。

4

2 回答 2

9

Thanks to the key information about the illegal characters in UTF-8 from @collapsar, as well as some digging by a co-worker, I've come up with this:

CREATE OR REPLACE FUNCTION reencode(string IN VARCHAR2) RETURN VARCHAR2
AS
    encoded VARCHAR2(32767);
    type  array_t IS varray(3) OF VARCHAR2(15);
    array array_t := array_t('AL32UTF8', 'WE8MSWIN1252', 'WE8ISO8859P1');
BEGIN
    FOR I IN 1..array.count LOOP
        encoded := CASE array(i)
            WHEN 'AL32UTF8' THEN string
            ELSE CONVERT(string, 'AL32UTF8', array(i))
        END;
        IF instr(
            rawtohex(
                utl_raw.cast_to_raw(
                    utl_i18n.raw_to_char(utl_raw.cast_to_raw(encoded), 'utf8')
                )
            ),
            'EFBFBD'
        ) = 0 THEN
            RETURN encoded;
        END IF;
    END LOOP;
    RAISE VALUE_ERROR;
END;

Curiously, it never gets to WE8ISO8859P1: WE8MSWIN1252 converts every single one of the list of 800 or so bad values I have without complaint. The same is not true for my Perl or PostgreSQL implementations, where CP1252 fails for some values but ISO-8859-1 succeeds. Still, the values from Oracle seem adequate, and appear to be valid Unicode (tested by loading them into PostgreSQL), so I can't complain. This will be good enough to sanitize my data, I think.

于 2012-10-10T00:15:16.730 回答
3

要检查您的数据库列是否包含无效的 utf-8,请使用以下查询:

 select CASE
            INSTR (
                  RAWTOHEX (
                      utl_raw.cast_to_raw (
                          utl_i18n.raw_to_char (
                                utl_raw.cast_to_raw ( <your_column> )
                              , 'utf8'
                          )
                      )
                  )
                , 'EFBFBD'
            )
        WHEN 0 THEN 'OK'
        ELSE 'FAIL' 
        END
   from <your_table>
      ;

鉴于您的数据库字符集是 al32utf8。

请注意,这EF BF BD表示utf-8 中的非法编码

由于您指示的所有其他字符集都是面向字节的,因此转换为 unicode 永远不会失败,但可能会产生不同的代码点。如果没有上下文信息,就不可能自动确定实际的源字符集。

最好的问候,卡斯滕

ps:字符集的oracle名称: CP1252-> WE8MSWIN1252 LATIN-1->WE8ISO8859P1

于 2012-10-09T11:47:39.767 回答