dbms_lob.substr 的迁移脚本得到 "character string buffer too small"
Migration script with dbms_lob.substr gets "character string buffer too small"
我在将数据从旧的 table 迁移到新的脚本时遇到问题。旧 table 中的其中一列是 CLOB,但在新 table 中它是 VARCHAR2。我尝试使用下面的代码插入那些。但是我遇到了错误的麻烦:
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
如果您的源列实际上是 NCLOB 而不是 CLOB,您将收到此错误。这没关系:
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作为数据库和国家字符集。
因此,如果您的来源 table 是 NCLOB,您只能提取前 2000 个字符以放入您的 stream_text
table.
如果源列是 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 个字节是多字节字符的中途,您可能仍然会遇到问题)。
我猜到了目标 table 中的列名,所以显然要使用真实的列名。如果您有大量数据,则进行批量插入会更好;提取到集合中并使用 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;
/
我在将数据从旧的 table 迁移到新的脚本时遇到问题。旧 table 中的其中一列是 CLOB,但在新 table 中它是 VARCHAR2。我尝试使用下面的代码插入那些。但是我遇到了错误的麻烦:
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
如果您的源列实际上是 NCLOB 而不是 CLOB,您将收到此错误。这没关系:
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作为数据库和国家字符集。
因此,如果您的来源 table 是 NCLOB,您只能提取前 2000 个字符以放入您的 stream_text
table.
如果源列是 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 个字节是多字节字符的中途,您可能仍然会遇到问题)。
我猜到了目标 table 中的列名,所以显然要使用真实的列名。如果您有大量数据,则进行批量插入会更好;提取到集合中并使用 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;
/