【问题标题】:How can I convert Oracle VARCHAR2 values to UTF-8 from a list of possible encodings?如何从可能的编码列表中将 Oracle VARCHAR2 值转换为 UTF-8?
【发布时间】:2012-09-24 21:42:39
【问题描述】:

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

  • US-ASCII
  • UTF-8
  • CP1252
  • Latin-1

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

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

因此,我有两个问题:

  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 中做同样的事情。

【问题讨论】:

    标签: oracle unicode utf-8 character-encoding


    【解决方案1】:

    要检查您的数据库列是否包含无效的 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>
          ;
    

    假设您的 db 字符集是 al32utf8。

    注意EF BF BD 代表illegal encoding in utf-8

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

    最好的问候,卡斯滕

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

    【讨论】:

    • 是的,我们不知道原始字符集是什么,所以我只想让值 UTF-8 干净。根据您的建议以及同事的初步实施,我提出了一个我认为非常接近我需要的功能。我将在单独的答案中发布。
    【解决方案2】:

    感谢@collapsar 提供的有关 UTF-8 中非法字符的关键信息,以及同事的一些挖掘,我想出了这个:

    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;
    

    奇怪的是,它永远不会到达 WE8ISO8859P1:WE8MSWIN1252 可以毫无怨言地转换 800 个左右的坏值列表中的每一个。对于我的 Perl 或 PostgreSQL 实现,情况并非如此,其中 CP1252 对某些值失败,但 ISO-8859-1 成功。尽管如此,来自 Oracle 的值似乎足够了,并且似乎是有效的 Unicode(通过将它们加载到 PostgreSQL 中进行测试),所以我不能抱怨。我认为这足以清理我的数据。

    【讨论】:

    • 您的代码中发生的情况是您首先尝试通过调用“convert”将输入数据转换为al32utf8,然后检查操作是否成功。但是,对于面向字节的字符集——cp1252 恰好是,每个编码的长度正好是 1 个字节——转换为 unicode 永远不会失败。因此您的检查将成功并且函数reencode 将退出。请注意,通过成功转换为 unicode 来区分字节编码的源字符集是不可能的 - 您需要上下文信息才能做到这一点。问候。
    • (续)。 1.) 从技术上讲,我的声明仅适用于其字形被合并到 unicode 中的(字节编码的)字符集。我不知道任何不符合此标准的字符集(提示赞赏)。 2.) 要识别源字符集,您可以 2a.) 在 latin-1 与 cp1252 的特定情况下,检查未映射到 latin-1 (0x7f-0x9f) 或 2b 中的字形的字节。) 通常检查序列而不是单个字符。例如:A4 -> EURO (latin-15) / CURRENCY (cp1252)。后者不会出现在普通文本中的数字之后,因此 &lt;numbers&gt; A4 将表示 latin-15。
    • 唉,我没有上下文信息,所以我只是清理一些旧的东西。在超过 10 亿条记录中,有几千条转换为 CP1252 是我们可以忍受的。
    猜你喜欢
    • 2011-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-30
    • 2011-06-26
    • 2014-02-02
    • 2012-06-12
    相关资源
    最近更新 更多