ORA-01461:如果输入字符串是>,则SUBSTR()返回LONG。 4000个字符?

时间:2017-08-22 15:36:27

标签: oracle type-conversion oracle12c

我有一个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;

2 个答案:

答案 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的情况是肯定的。我明天会做一个快速测试。