雙位元組中文字符集匯出的DMP檔案匯入UTF8字符集

sundog315發表於2005-03-28
    目前Oracle對中文的支援已經做的很好,有很多字符集可以儲存中文字元,常用的有ZHS16GBK、UTF8等等。而對於除UTF8字符集外,其他字符集在儲存中文字元時將會使用兩個位元組,但如果使用UTF8,則需要佔用3個位元組的空間。因此,問題來了,如果我們將兩個位元組的字符集中的資料匯入UTF8字符集時是否會出現問題?怎麼解決該問題?[@more@]

    我們先來做個實驗:

    D:>sqlplus /nolog

    SQL*Plus: Release 8.1.7.0.0 - Production on 星期一 3月 28 15:02:56 2005
    (c) Copyright 2000 Oracle Corporation.  All rights reserved.
  
    SQL> connect
    已連線。

    SQL> select *
      2    from v$nls_parameters;
   
    PARAMETER   VALUE
    ----------------------------------------------
    NLS_LANGUAGE  SIMPLIFIED CHINESE
    NLS_TERRITORY  CHINA
    NLS_CURRENCY  RMB
    NLS_ISO_CURRENCY  CHINA
    NLS_NUMERIC_CHARACTERS .,
    NLS_CALENDAR  GREGORIAN
    NLS_DATE_FORMAT  DD-MON-RR
    NLS_DATE_LANGUAGE  SIMPLIFIED CHINESE
    NLS_CHARACTERSET  ZHS16GBK
    NLS_SORT   BINARY
    NLS_TIME_FORMAT  HH.MI.SSXFF AM
    NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
    NLS_TIME_TZ_FORMAT  HH.MI.SSXFF AM TZH:TZM
    NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZH:TZM
    NLS_DUAL_CURRENCY  RMB
    NLS_NCHAR_CHARACTERSET ZHS16GBK
    NLS_COMP   BINARY

    已選擇17行。

    SQL> select status from alac_accounting_periods
      2   where rownum=1;
   
    STATUS
    --------------------
    關閉
   
    SQL> select dump(status) from alac_accounting_periods
      2   where rownum=1;
   
    DUMP(STATUS)
    -----------------------------------------------------------------------
   
    Typ=1 Len=4: 185,216,177,213
   
    這裡我們看出,字串“關閉”在ZHS16GBK字符集中佔用了4個位元組。

    SQL> connect
    已連線。

    SQL> select *
      2    from v$nls_parameters;

    PARAMETER   VALUE
    ----------------------------------------------
    NLS_LANGUAGE  SIMPLIFIED CHINESE
    NLS_TERRITORY  CHINA
    NLS_CURRENCY  RMB
    NLS_ISO_CURRENCY  CHINA
    NLS_NUMERIC_CHARACTERS .,
    NLS_CALENDAR  GREGORIAN
    NLS_DATE_FORMAT  DD-MON-RR
    NLS_DATE_LANGUAGE  SIMPLIFIED CHINESE
    NLS_CHARACTERSET  UTF8
    NLS_SORT   BINARY
    NLS_TIME_FORMAT  HH.MI.SSXFF AM
    NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
    NLS_TIME_TZ_FORMAT  HH.MI.SSXFF AM TZH:TZM
    NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZH:TZM
    NLS_DUAL_CURRENCY  RMB
    NLS_NCHAR_CHARACTERSET AL16UTF16
    NLS_COMP   BINARY
    NLS_LENGTH_SEMANTICS BYTE
    NLS_NCHAR_CONV_EXCP  FALSE

    已選擇19行。

    SQL> select status from alac_accounting_periods
      2   where rownum=1;

    STATUS
    ------------------------------------------------------------
    關閉

    SQL> select dump(status) from alac_accounting_periods
      2   where rownum=1;

    DUMP(STATUS)
    -----------------------------------------------------------------------

    Typ=1 Len=6: 229,133,179,233,151,173

    這裡可以看出,相同的字串,在UTF8字符集裡佔用了6個位元組。
    我們可以預測,如果將ZHS16GBK的資料進行匯出,並且匯入UTF8字符集的話,則有可能原先可以儲存在ZHS16GBK字符集下的字串因為實際佔用位元組增加而超過列定義的大小,導致匯入失敗。
    Oracle提供了一個檢測字符集衝突的工具csscan:
    利用這個工具可以檢查出那些資料在進行字符集轉換時可能會出問題。這裡不再祥述。
    繼續我們的實現,看看是否會出現剛才預測的問題。在匯入的過程中確實出現了錯誤:

    IMP-00019: 行被拒絕是因為 ORACLE 錯誤1401
    IMP-00003: ORACLE 錯誤1401出現
    ORA-01401: 插入的值對於列過大
    列 1 012
    列 2 E54
    列 3
    列 4 05-20
    列 5 200405
    列 6 1
    列 7 14005
    列 8 B747-200F
    列 9 52155.58
    列 10 52155.58
    列 11 外航新開帳單
    列 12

    再看看這個:
    SQL> desc ALAC_ACCA_CR_SNAP
    名稱                                      空?      型別
    ----------------------------------------- -------- --------------
   
    開帳公司                                           VARCHAR2(20)
    被開帳公司                                         VARCHAR2(20)
    航站                                               VARCHAR2(3)
    帳單號                                             VARCHAR2(30)
    外航清算月                                         VARCHAR2(50)
    序號                                               NUMBER
    服務專案                                           VARCHAR2(15)
    機型                                               VARCHAR2(10)
    帳單金額                                           NUMBER
    接受金額                                           NUMBER
    CASE                                               VARCHAR2(12)
    SNAP_MONTH                                         VARCHAR2(6)
    我們發現第11列,即CASE列的大小為12,如果在兩位元組字符集中,剛好能夠儲存,但在匯入UTF8時將會出錯。
    ORACLE官方的解決方案是:利用csscan來查詢,然後更改表的定義。但是這個方法比較麻煩。有沒有簡單點的方法,對DMP檔案動動手腳,讓他能夠匯入呢?
    既然是由於列定義大小過小而導致此問題,那麼應該可以修改DMP檔案中CREATE TABLE SQL中對於字串列定義的大小,從而能夠直接匯入修改後的DMP檔案。
    常用的字串型別有:Varchar2,Nvarchar2,char等,在本例中,中文字元都儲存在varchar2型別中,所以只需對varchar2進行處理。我們需要讀取DMP檔案中varchar2大小T,然後將T*2/3向上取整後的數字寫回DMP檔案即可。這裡附加上我用Delphi寫的處理程式。
相關工具下載:
   

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/19423/viewspace-794183/,如需轉載,請註明出處,否則將追究法律責任。

相關文章