2015-05-28 160 views
1

我有一個腳本,將數據從舊錶遷移到新的問題。舊錶中的一列是CLOB,但在新表中是VARCHAR2。我試圖插入使用下面的代碼a。但我惹了麻煩,錯誤:遷移腳本與dbms_lob.substr獲取「字符串緩衝區太小」

ORA-06502: PL/SQL: numeric or value error: character string buffer too small.

DECLARE 
    CURSOR CUR IS 
     SELECT T.* 
      FROM ACTIVITY_EVENT T 
     WHERE T.POST_TEXT IS NOT NULL; 
    R CUR%ROWTYPE; 
BEGIN 
FOR R IN CUR 
    LOOP 
     INSERT INTO STREAM_TEXT 
      WITH STR AS 
      (SELECT T.* 
       FROM STREAM T 
       WHERE T.OLD_ID = R.ID) 
      SELECT SEQ$STREAM_TEXT.NEXTVAL AS ID, 
        DBMS_LOB.SUBSTR(T.POST_TEXT, 4000, 1) AS TEXT, 
        T.DT AS DT, 
        'READY' AS STATE, 
        STR.ID AS STREAM_ID 
       FROM ACTIVITY_EVENT T 
       LEFT JOIN STR 
       ON STR.OLD_ID = T.ID 
      WHERE T.POST_TEXT IS NOT NULL 
       AND STR.OLD_ID = T.ID; 
    END LOOP; 
END; 

我第一次做這樣的代碼,而循環,得到了同樣的問題,所以我試圖創建一個循環。但結果是一樣的。

這個簡單的查詢失敗,出現同樣的錯誤:

SELECT T.ID, DBMS_LOB.SUBSTR(T.POST_TEXT, 4000, 1) 
FROM ACTIVITY_EVENT T 
WHERE T.POST_TEXT IS NOT NULL 
+1

請爲涉及的表添加DDL語句。另外,你有沒有嘗試在'INSERT'中添加列列表?在「INSERT」語句中省略列表是不好的做法 - 這將保證將來會導致維護問題。 –

+0

這會失敗,同樣的問題'SELECT t.id,DBMS_LOB.substr(t.post_text,4000,1)FROM ACTIVITY_EVENT t WHERE t.POST_TEXT IS NOT NULL' –

+0

我對請求做了一些改動。我認爲緩衝區大小的問題。但不知道如何清除緩衝區。 –

回答

3

,如果你的源列實際上是一個NCLOB,而不是CLOB你會得到這個錯誤。這是OK:

create table t42 (id number, dt date, post_text clob); 
insert into t42 (id, dt, post_text) values (1, sysdate, dbms_random.string('p', 4000)); 
select id, dbms_lob.substr(post_text, 4000, 1) from t42; 

但是這樣的錯誤,只是改變CLOB到NCLOB,任何長度大於2000:

create table t42 (id number, dt date, post_text nclob); 
insert into t42 (id, dt, post_text) values (1, sysdate, dbms_random.string('p', 4000)); 
select id, dbms_lob.substr(post_text, 4000, 1) from t42; 

SQL Error: ORA-06502: PL/SQL: numeric or value error: character string buffer too small 
ORA-06512: at line 1 

這是AL32UTF8和AL16UTF16作爲數據庫和國家字符集。

所以如果你的源表是NCLOB,你只能提取前2000個字符放到你的stream_text表中。

如果源列是CLOB並且前4000個字符包含任何多字節字符,那麼您還會看到這一點。 dbms_log.substr(x, 4000, 1)總是獲取CLOB的前4000個字符,該字符可能超過4000個字節 - 這是SQL上下文中VARCHAR2值的最大大小,即使它聲明爲varchar2(4000 char),因爲它仍然不能超過4000-字節限制。

如果你想獲得最大的4000個字符了,那麼你可以做到這一點通過PL/SQL VARCHAR2變量,另外還有substrb()電話:

DECLARE 
    CURSOR CUR IS 
     SELECT SEQ$STREAM_TEXT.NEXTVAL AS ID, 
       T.POST_TEXT, 
       T.DT AS DT, 
       'READY' AS STATE, 
       S.ID AS STREAM_ID 
      FROM ACTIVITY_EVENT T 
      LEFT JOIN STREAM S 
      ON S.OLD_ID = T.ID 
     WHERE T.POST_TEXT IS NOT NULL; 

    TMP_TEXT VARCHAR2(4000); 
BEGIN 
    FOR R IN CUR 
    LOOP 
     TMP_TEXT := SUBSTRB(DBMS_LOB.SUBSTR(R.POST_TEXT, 4000, 1), 1, 4000); 
     INSERT INTO STREAM_TEXT (ID, TEXT, DT, STATE, STREAM_ID) 
     VALUES (R.ID, TMP_TEXT, R.DT, R.STATE, R.STREAM_ID); 
    END LOOP; 
END; 
/

substrb(..., 1, 4000)部分將無法在SQL工作,要麼,因爲內部表達仍然過大,但它在PL/SQL中起作用。您將獲得前4000個字符的前4000個字節。 (儘管如果第4000個字節是通過多字節字符的中途,您仍然可能會遇到問題)。

我猜對了目標表中的列名,所以很明顯使用真正的列名。如果你有大量的數據,做批量插入會更好;獲取到一個集合中,並使用FORALL來批量插入而不是逐行插入;像這樣的東西可以作爲一個起點:

DECLARE 
    TYPE TMP_REC_TYPE IS RECORD (
     ID STREAM_TEXT.ID%TYPE, 
     POST_TEXT ACTIVITY_EVENT.POST_TEXT%TYPE, 
     TEXT STREAM_TEXT.TEXT%TYPE, 
     DT STREAM_TEXT.DT%TYPE, 
     STATE STREAM_TEXT.STATE%TYPE, 
     STREAM_ID STREAM_TEXT.STREAM_ID%TYPE 
    ); 
    TYPE TMP_REC_TAB_TYPE IS TABLE OF TMP_REC_TYPE; 
    TMP_REC_TAB TMP_REC_TAB_TYPE; 
    RC SYS_REFCURSOR; 
BEGIN 
    OPEN RC FOR 
     SELECT SEQ$STREAM_TEXT.NEXTVAL AS ID, 
       T.POST_TEXT, 
       NULL AS TEXT, 
       T.DT AS DT, 
       'READY' AS STATE, 
       S.ID AS STREAM_ID 
      FROM ACTIVITY_EVENT T 
      LEFT JOIN STREAM S 
      ON S.OLD_ID = T.ID 
     WHERE T.POST_TEXT IS NOT NULL; 
    LOOP 
     FETCH RC BULK COLLECT INTO TMP_REC_TAB LIMIT 100; 
     FOR I IN 1..TMP_REC_TAB.COUNT LOOP -- populate text field 
      TMP_REC_TAB(I).TEXT := SUBSTRB(
       DBMS_LOB.SUBSTR(TMP_REC_TAB(I).POST_TEXT, 4000, 1), 1, 4000); 
     END LOOP; 
     FORALL I IN 1..TMP_REC_TAB.COUNT -- bulk insert 
      INSERT INTO STREAM_TEXT (ID, TEXT, DT, STATE, STREAM_ID) 
      VALUES (TMP_REC_TAB(I).ID, TMP_REC_TAB(I).TEXT, TMP_REC_TAB(I).DT, 
       TMP_REC_TAB(I).STATE, TMP_REC_TAB(I).STREAM_ID); 
     EXIT WHEN RC%NOTFOUND; 
    END LOOP; 
END; 
/
+0

非常感謝你。你拯救了我的一天。 –

+0

我檢查了它是CLOB的列。:/ –

+0

@ОлександрСамсонов - OK;無論如何,這種方法是否解決了問題? –