2012-10-03 65 views
8

由於遺留原因,我們在Oracle 10數據庫中有一個VARCHAR2列 - 其中字符編碼設置爲AL32UTF8 - 它包含一些非UTF-8值。值始終是在這些字符集之一:如何從可能的編碼列表中將Oracle VARCHAR2值轉換爲UTF-8?

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

我寫了一個Perl的功能修復數據庫外部的損壞值。對於來自此數據庫列的值,它循環遍歷此編碼列表並嘗試將值轉換爲UTF-8。如果轉換失敗,它會嘗試下一個編碼。第一個沒有錯誤地轉換是我們保留的價值。現在,我想在數據庫中複製這個功能,以便任何人都可以使用它。

但是,我所能找到的這個是CONVERT function,它永遠不會失敗,但會插入替換字符來替換它不識別的字符。因此,就我所知,無法知道轉換何時失敗。

爲此,我有兩個問題:

  1. 有一些試圖將字符串轉換成編碼的列表中的一個,這回成功的第一個現有的接口?
  2. 如果沒有,是否有一些其他接口指示失敗,如果它無法將字符串轉換爲編碼?如果是這樣,那麼我可以寫上一個函數。

UPDATE:

僅供參考,我已經寫在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中做同樣的事情。

回答

6

感謝來自@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; 

Cu很難說,它永遠不會到WE8ISO8859P1:WE8MSWIN1252轉換800個左右列表中的每一個,我沒有任何抱怨。我的Perl或PostgreSQL實現也是如此,其中CP1252在某些值上失敗,但ISO-8859-1成功。儘管如此,來自Oracle的價值似乎已經足夠,並且似乎是有效的Unicode(通過將它們加載到PostgreSQL進行測試),所以我無法抱怨。我認爲,這將足以清理我的數據。

+0

你的代碼發生了什麼,你*首先*嘗試通過調用'convert'來轉換你的輸入數據到al32utf8,然後檢查操作是否成功。然而,對於字節爲導向的字符集 - 哪些cp1252碰巧是,每個編碼的長度恰好爲1個字節 - 轉換爲unicode將永遠不會失敗。因此你的檢查將會成功,'reencode'功能將退出。請注意,通過成功轉換爲unicode的方式來區分字節編碼的源字符集是不可能的 - 您需要上下文信息來執行此操作。問候。 – collapsar

+0

(續)。 1.)從技術上講,我的陳述僅適用於字形被併入unicode的(字節編碼)字符集。我不知道任何不符合這個標準的字符集(提示讚賞)。 2.)來標識源字符集,你可以2a。)在latin-1和cp1252的特定情況下,檢查未在latin-1(0x7f-0x9f)或2b中映射到字形的字節。序列而不是單個字符。例如:A4 - >歐元(拉丁文-15)/貨幣(cp1252)。後者不會出現在普通文本中的數字之後,所以' A4'將表示拉丁文-15。 – collapsar

+0

唉,我沒有上下文信息,所以我只是在清理一些舊東西。在十億條記錄中轉換爲CP1252是我們可以生活的。 – theory

2

檢查您的數據庫列是否包含無效的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代表illegal encoding in utf-8

由於您指出的所有其他字符都是面向字節的,因此轉換爲unicode將永遠不會失敗,但可能會產生不同的代碼點。沒有上下文信息將不可能自動確定實際的源字符集。

最好的問候,卡斯滕

PS:對於字符集 甲骨文名稱: CP1252 - >WE8MSWIN1252 LATIN-1 - >WE8ISO8859P1

+0

是的,我們不知道原始字符集是什麼,所以我只想得到值UTF-8清潔。根據你的建議,以及一位同事的初步實施,我想出了一個我認爲非常接近我需要的功能。我會在一個單獨的答案中發佈它。 – theory

相關問題