【问题标题】:ORA-01461: Does SUBSTR() return a LONG if the input string is > 4000 characters?ORA-01461: 如果输入字符串大于 4000 个字符,SUBSTR() 是否返回 LONG?
【发布时间】:2017-08-22 15:36:26
【问题描述】:

我有一个 NoteDetail 表,其中 NoteText 的 varchar(4000) 字段和 NoteNumber 的 number(4,0) 字段。这个想法是,如果一个超过 4000 个字符的音符出现,它会被分成多个音符,并带有递增的 NoteNumber 条目。

执行插入的逻辑如下,它在 Oracle 10 上运行良好。最近该应用程序已移至 Oracle 12c,我收到错误消息:“ORA-01461: can bind a LONG value only for insert into a LONG column" Oracle DBA 无法弄清楚为什么会发生这种情况。

以下是我的插入代码的本质;我最好的猜测是,如果传入的字符串足够长,那么即使我分配给 varchar2(4000) 类型的变量,SUBSTR() 函数也会返回一个 long。供参考:Oracle SQL Developer 指出错误发生在插入点(在下面 else 块的循环中)。

有谁知道如何解决这个问题?

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;

表架构是:

    DCLARE TABLE NoteDetail (
      NoteId      number(19,0),
      NoteNumber  number(4,0),
      NoteText    varchar2(4000)
    );

【问题讨论】:

  • 请包含重现问题所需的最短代码。还请在表格中附加一个结构。目前只有你的 cmets 而不是真正的代码和insert 语句。
  • 我添加了架构和更多代码。
  • 对于投票结束这个的人,你能至少解释一下原因吗?我现在才在我的职业生涯中第一次遇到 Oracle,我必须让这些样本如此通用,这样我才不会因为泄露公司信息而被解雇。请让我在这里休息一下。
  • 你这样做的方式看起来有点工匠精神(即:在 CLOB 发明之前,有点像 1980 年代):为什么不改用 CLOB?
  • 如果您的文本列最大为 4000 字节,请尝试使用 SUBSTRB 和 LENGTHB 函数。也必须小心把它拼回来

标签: oracle type-conversion oracle12c


【解决方案1】:

如果您的文本列最大为 4000 字节,请尝试使用 SUBSTRB 和 LENGTHB 函数。你也必须小心把它拼回来。

基本上它归结为多字节字符编码。如果您使用 utf8 并且您的文本包含多字节字符,则 4000 个字符超过 4000 个字节(这是您的表格列中的最大值)。您可以按照 krokodilko 的建议将其更改为 4000 字符,或将数据拆分为 4000 字节的块(这是 substrb 和 lengthb 函数所做的)。

请注意,如果您的截止值恰好是 4000 字节,则您可能会遇到 4000 字节块末尾的多字节字符问题,具体取决于您希望如何显示数据。如果你先把大块拼凑起来应该没问题,但要测试这个栅栏箱以确保。明天我会自己做一个快速测试。

【讨论】:

  • 当我开始对 LENGTH() 和 LENGTHB() 进行 dbms_output.put_line() 比较时,发生的事情立即变得显而易见。现在我看到还有一个 LENGTH4() 函数......至少我现在知道存在多种字符集类型并且会影响我的代码。 SQL Server 中没有这种复杂性:对于简单的 ASCII 文本,只有 varchar,对于 unicode,只有 nvarchar。
【解决方案2】:

这不是您问题的确切答案,而是我想向您展示的一些实验。
我的 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);

【讨论】:

    猜你喜欢
    • 2014-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-14
    相关资源
    最近更新 更多