我有一个NoteDetail表,其中NoteText的varchar(4000)字段和NoteNumber的数字(4,0)。这个想法是,如果一个长度超过4000个字符的音符被分成多个音符,并且会增加NoteNumber条目。
执行插入的逻辑在下面,它在Oracle 10上运行得很好。最近,应用程序被移动到Oracle 12c,我收到了错误:" ORA-01461:可以绑定一个LONG值仅用于插入LONG列" Oracle DBA无法弄清楚为什么会发生这种情况。
以下是我的插入代码的本质;我最好的猜测是,如果传入的字符串足够长,那么即使我分配给varchar2(4000)类型的变量,SUBSTR()函数也会返回一个长整数。供参考:Oracle SQL Developer指示错误发生在插入点(在下面的else块的循环中)。
有谁知道如何解决这个问题?
WITH Splitting AS
(
SELECT teststring AS [value]
FROM @input
)
WITH Spaces AS
(
SELECT Spaced.[value], ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY (SELECT 1)) AS ordinal
FROM Splitting AS sp
CROSS APPLY STRING_SPLIT(sp.[value], ' ') AS Spaced
)
, Tabs AS
(
SELECT Tabbed.[value], ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY s.ordinal, (SELECT 1)) AS ordinal
FROM Spaces AS s
CROSS APPLY STRING_SPLIT(s.[value], ' ') AS Tabbed
)
, NewLines1 AS
(
SELECT NewLined1.[value], ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY t.ordinal, (SELECT 1)) AS ordinal
FROM Tabs AS t
CROSS APPLY STRING_SPLIT(t.[value], CHAR(13)) AS NewLined1
)
, NewLines2 AS
(
SELECT NewLined2.[value], ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY nl1.ordinal, (SELECT 1)) AS ordinal
FROM NewLines1 AS nl1
CROSS APPLY STRING_SPLIT(nl1.[value], CHAR(10)) AS NewLined2
)
, Splitted AS
(
SELECT LTRIM(RTRIM(nl2.[value])) AS [teststring], ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY nl2.ordinal, (SELECT 1)) AS ordinal
FROM NewLines2 AS nl2
WHERE LTRIM(RTRIM(nl2.[value])) <> ''
)
SELECT *
FROM (SELECT [value], 'part' + CONVERT(nvarchar(20), [ordinal]) AS [parts] FROM Splitted) AS s
PIVOT (MAX([value]) FOR [parts] IN ([part1], [part2], [part3], [part4])
表架构是:
DECLARE
s_incoming_string varchar2(32000);
s_substring_value varchar2(4000);
i_note_iteration number(4,0);
i_note_iterations number(4,0);
i_substr_start number(6,0);
k_NoteId number(19,0);
BEGIN
SELECT noteid_seq.nextval INTO k_NoteId FROM dual;
s_incoming_string := {a 7000 character long note};
i_note_iterations := ceil(length(s_incoming_string)/4000);
IF i_note_iteration = i_note_iterations THEN
--I NEVER GET AN ERROR ON THIS BRANCH!!!
INSERT INTO NoteDetail (NoteId, NoteNumber, NoteText)
VALUES (k_NoteId, 1, s_incoming_string);
ELSE
FOR i_note_iteration IN 1..i_note_iterations
LOOP
i_substr_start := (4000 * (i_note_iteration - 1)) + 1;
IF i_note_iteration = i_note_iterations THEN
--this is the last chunk of text; no need
--to read past the end of the buffer
s_substring_value = SUBSTR(s_incoming_string, i_substr_start);
ELSE
--I ONLY GET AN ERROR if this branch is executed before the insert:
s_substring_value = SUBSTR(s_incoming_string, 4000);
END IF;
INSERT INTO NoteDetail (NoteId, NoteNumber, NoteText)
VALUES (k_NoteId, i_note_iteration, s_incoming_string);
END LOOP;
END IF;
END;
答案 0 :(得分:1)
这不是您问题的准确答案,而是我想向您展示的一些实验 我的Oracle 12c安装与您的设置相同:
SELECT * FROM NLS_DATABASE_PARAMETERS
WHERE parameter = 'NLS_LENGTH_SEMANTICS'
OR parameter like '%SET%'
PARAMETER VALUE
-------------------------- ------------
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_CHARACTERSET AL32UTF8
NLS_LENGTH_SEMANTICS BYTE
现在检查一下这段代码:
DECLARE
s_incoming_string varchar2(32000);
s_substring_value varchar2( 4000 );
BEGIN
LOOP
s_incoming_string := s_incoming_string || 'żaba';
exit when length( s_incoming_string ) > 4500;
END LOOP;
DBMS_OUTPUT.put_line( 'Length in characters = ' || length( s_incoming_string ) );
DBMS_OUTPUT.put_line( 'Length in bytes = ' || lengthb( s_incoming_string ) );
s_substring_value := substr( s_incoming_string, 1, 4000 );
DBMS_OUTPUT.put_line( 'Length in characters = ' || length( s_substring_value ) );
DBMS_OUTPUT.put_line( 'Length in bytes = ' || lengthb( s_substring_value ) );
END;
/
如果我运行上面的代码,我得到以下输出:
Length in characters = 4504
Length in bytes = 5630
Error report -
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 12
06502. 00000 - "PL/SQL: numeric or value error%s"
*Cause: An arithmetic, numeric, string, conversion, or constraint error
occurred. For example, this error occurs if an attempt is made to
assign the value NULL to a variable declared NOT NULL, or if an
attempt is made to assign an integer larger than 99 to a variable
declared NUMBER(2).
*Action: Change the data, how it is manipulated, or how it is declared so
that values do not violate constraints.
现在,如果我将s_substring_value
的声明更改为varchar2( 4000 char );
,则代码可以正常运行,并且会产生以下结果:
DECLARE
s_incoming_string varchar2(32000);
s_substring_value varchar2( 4000 char );
BEGIN
LOOP
s_incoming_string := s_incoming_string || 'żaba';
exit when length( s_incoming_string ) > 4500;
END LOOP;
DBMS_OUTPUT.put_line( 'Length in characters = ' || length( s_incoming_string ) );
DBMS_OUTPUT.put_line( 'Length in bytes = ' || lengthb( s_incoming_string ) );
s_substring_value := substr( s_incoming_string, 1, 4000 );
DBMS_OUTPUT.put_line( 'Length in characters = ' || length( s_substring_value ) );
DBMS_OUTPUT.put_line( 'Length in bytes = ' || lengthb( s_substring_value ) );
END;
/
Length in characters = 4504
Length in bytes = 5630
Length in characters = 4000
Length in bytes = 5000
PL/SQL procedure successfully completed.
有关此问题的更多信息,请访问:NLS_LENGTH_SEMANTIC
注意:波兰字符ż
在UTF8中被编码为两个字节;
select dump('ż') as x from dual;
X
---------------------
Typ=96 Len=2: 197,188
所以字符串:żaba
(英文:frog)需要5个字节,而不是4个字节。
注2:您还需要更改表定义:
ALTER TABLE NoteDetail
MODIFY NoteText varchar2(4000 char);
答案 1 :(得分:1)
如果文本列最大为4000字节,请尝试使用SUBSTRB和LENGTHB函数。你也要小心拼凑它。
基本上它归结为多字节字符编码。如果您使用utf8并且文本包含多字节字符,则4000个字符超过4000个字节(这是表列中的最大值)。您可以像krokodilko建议的那样将其更改为4000个字符,或者将数据拆分为4000个字节的块(这是substrb和lengthb函数的作用)。
请注意,如果截止值恰好为4000字节,则可能在4000字节块的最末端出现多字节字符问题,具体取决于您希望如何显示数据。如果你首先将块重新组合在一起它应该没问题,但测试这个fencepost的情况是肯定的。我明天会做一个快速测试。