2017-08-22 73 views
1

我有一個NoteDetail表,NoteText的varchar(4000)字段和NoteNumber的number(4,0)。這個想法是,如果一個超過4000個字符的音符進來,它會被分成多個音符,並帶有遞增的NoteNumber條目。ORA-01461:如果輸入字符串> 4000個字符,SUBSTR()是否返回LONG?

執行插入操作的邏輯如下,它在Oracle 10上運行得非常漂亮。最近該應用程序被移動到了Oracle 12c,並且出現錯誤:「ORA-01461:只能將LONG值綁定爲插入一個LONG列「Oracle DBA無法弄清楚爲什麼會發生這種情況。

下面是我的插入代碼的本質;我最好的猜測是,如果傳入的字符串足夠長,那麼即使在分配給varchar2(4000)類型的變量時,SUBSTR()函數也會返回一個長整型。供參考: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) 
    ); 
+0

請包括重現問題所需的最短代碼。請在表格中追加結構。現在只有你的註釋,而不是真正的代碼和'insert'語句。 – krokodilko

+0

我添加了模式和更多的代碼。 –

+0

對於投票結束這件事的人們,你至少會解釋爲什麼?我現在只是在職業生涯中第一次遇到甲骨文,我不得不讓這些樣本變得如此通用,所以我不會因泄露公司信息而被解僱。請在這裏休息一下。 –

回答

1

如果您的文本列最大爲4000字節,請嘗試使用SUBSTRB和LENGTHB函數。你也必須小心將它拼回去。

基本上歸結爲多字節字符編碼。如果您使用的是utf8,並且您的文本包含多字節字符,那麼4000個字符將超過4000個字節(這是您的表列中的最大值)。您可以像krokodilko建議的那樣做,並將其更改爲4000個字符,或將數據拆分爲4000個字節的塊(這是substrb和lengthb函數的作用)。

請注意,根據您希望顯示數據的方式,如果您的截止值恰好爲4000個字節,則可能在4000字節塊的末尾存在多字節字符問題。如果你先將大塊拼到一起,它應該沒問題,但請確認這個籬笆箱是否合格。明天我會做一個快速測試。

+0

當我開始對LENGTH()和LENGTHB()進行dbms_output.put_line()比較時,它變得很明顯發生了什麼。現在我看到還有一個LENGTH4()函數......至少我現在知道存在多種charset類型並可能影響我的代碼。在SQL Server中沒有這種複雜性:只有varchar用於簡單的ASCII文本,而nvarchar用於unicode。 –

1

這不是一個準確的回答你的問題,而是一些實驗我想展現給你。
的My 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. 

現在如果我更改的聲明到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(英文:青蛙)需要5個字節,而不是4


注2:你也需要改變表的定義:

ALTER TABLE NoteDetail 
MODIFY NoteText varchar2(4000 char); 
相關問題