2015-05-28 115 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;无论如何,这种方法是否解决了问题? –