ORA-06502 when assign non english characters - oracle

I am facing a weird problem with oracle. I have a string which has some non English characters. I want to substring it to the length 10 and assign it to some variable. The substring function does it's job but the assignment fails with ORA-06502. It expects variable length to be 11.
Sample Code:
declare
userName varchar2(10);
begin
dbms_output.put_line('Length: ' || length(substr('JOHÑ NEWMAN',1,10)));
userName := substr('JOHÑ NEWMAN',1,10);
end;
Output:
Length: 10
ORA-06502: PL/SQL: numeric or value error: character string buffer too small

By default (unless you've set nls_length_semantics to char), varchar2(10) allocates 10 bytes of storage. Depending on your database character set and your data, that may be sufficient for 10 characters or it may be sufficient for many fewer characters.
You can declare your variable as varchar2(10 char) to allocate space for 10 characters regardless of the length in bytes. And you can use lengthb to see the length of a string in bytes. This should show that your string is 10 characters and 11 bytes in length.
declare
userName varchar2(10 char);
begin
dbms_output.put_line('Length: ' || length(substr('JOHÑ NEWMAN',1,10)));
dbms_output.put_line('LengthB: ' || lengthb(substr('JOHÑ NEWMAN',1,10)));
userName := substr('JOHÑ NEWMAN',1,10);
end;
/

The Oracle SUBSTR method is not multibyte character compatible.
You have two options:
Use SUBSTRB, which is multibyte character compatible.
Change the NLS_LENGTH_SEMANTICS setting to CHAR (default is BYTE)
See this post from dba-oracle.com for reference.

Related

What is the reason of using 'CHAR' with DATATYPE SIZE when declaring variable

WHY THE 'CHAR' IS USING WITH SIZE ?
declare
lf constant varchar2(1 char) := chr(10);
lf2 constant varchar2(2 char) := lf || lf;
begin
dbms_output.put_line('LINE-X'||lf||'LINE-Y'||lf2||'LINE-Z');
end;
RESULT IS FINE AS BELOW
LINE-X
LINE-Y
LINE-Z
PL/SQL procedure successfully completed.
AND IF IT IS POSIBLE WHICH SINARIO WE CAN USE 'CHAR'?
When declaring a varchar2 datatype variable (or column), you have two options when setting its size: char and byte:
SQL> create table test
2 (a varchar2(1 byte),
3 b varchar2(2 char)
4 );
Table created.
SQL>
The first option (byte) means that you reserved exactly one byte for that column.
The second option (char) means that you chose to store one character into that column. In many/most cases, there's no difference. But, if you use character set that contains multibyte characters, you can't tell how many bytes will each character occupy so - for example - varchar2(2 byte) doesn't guarantee that you'll manage to store all characters (for example the one that uses 3 bytes won't fit).

How to insert 2 million characters?

I need to insert 2 million characters in an Oracle database, but when I try to insert with a CLOB I have an error because its very long.
How can I insert 2 million characters in a field?
CREATE TABLE TABLA_CLOB(id number, valor CLOb default EMPTY_CLOB());
insert into TABLA_CLOB(id, valor) values (1,'DOCUMENT: ');
DECLARE
v_clob CLOB;
BEGIN
SELECT valor into v_clob from TABLA_CLOB where id=1 for update;
dbms_lob.writeappend(v_clob,200,'text with 2 million characters');
DBMS_OUTPUT.PUT_LINE(v_clob);
COMMIT;
END;
In PL/SQL & SQL, 'a value' is a text literal and the SQL documentation states:
Text literals have properties of both the CHAR and VARCHAR2 datatypes:
Within expressions and conditions, Oracle treats text literals as though they have the datatype CHAR by comparing them using blank-padded comparison semantics.
A text literal can have a maximum length of 4000 bytes.
Or for PL/SQL:
A string literal can hold up to 32,767 characters.
Split your 'text with 2 million characters' into 4k or 32k chunks and append each of those.

Create Oracle XMLTYPE from CLOB specifying character set

I'm trying to create XMLTYPE from CLOB column and specify character set explicitly. I've found there is an overloaded XMLTYPE.createXML function which accepts character set but when I execute passing additional arguments I get an error. Why?
SELECT
XMLTYPE.createXML(TO_CLOB ('<node1><node2>the ´ character</node2></node1>'),NLS_CHARSET_ID('AL32UTF8'),'',1,1)
from dual;
error:
ORA-06553: PLS-306: wrong number or types of arguments in call to
'CREATEXML'
The reason why I bother passing the character set is, CLOB column contains characters which are encoded with different character set than the database character set (which for example doesn't support #180).
The reason why I bother passing the character set is, CLOB column contains characters which are encoded with different character set than the database character set (which for example doesn't support #180).
This one I don't understand. #180; is simple plain ASCII, it should work for any condition.
Simply run
SELECT
XMLTYPE.createXML(TO_CLOB('<node1><node2>the ´ character</node2></node1>'))
from dual;
or even shorter
SELECT
XMLTYPE('<node1><node2>the ´ character</node2></node1>')
from dual;
Now, let's assume your XML contains characters which are not supported by database character set, in this case your XML could be <node1><node2>the ´ character</node2></node1> for example.
First of all you cannot store (or use) any character in CLOB (or VARCHAR2) which is not supported by database character set - never! You must use NCLOB (or NVARCHAR2) which are based on National Database Character Set and typically support any Unicode character.
You can specify character set in XMLTYPE.createXML(), however then you must provide XML as BLOB. You could do it like this:
DECLARE
xmlString NCLOB := '<node1><node2>the '||NCHR(180)||' character</node2></node1>';
xmlDoc XMLTYPE;
xmlBinary BLOB;
lang_context INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;
dest_offset INTEGER := 1;
src_offset INTEGER := 1;
read_offset INTEGER := 1;
warning INTEGER;
BEGIN
DBMS_LOB.CREATETEMPORARY(xmlBinary, TRUE);
DBMS_LOB.CONVERTTOBLOB(xmlBinary, xmlString, DBMS_LOB.LOBMAXSIZE, dest_offset, src_offset, 2000, lang_context, warning);
xmlDoc := XMLTYPE.createXML(xmlBinary, 2000, NULL, 1, 1);
END;
2000 is the csid of your national database character set. Use
SELECT PARAMETER, VALUE, NLS_CHARSET_ID(VALUE)
FROM NLS_DATABASE_PARAMETERS
WHERE PARAMETER LIKE '%CHARACTERSET';
to get your ID's.
Some notes:
I tried with string N'<node1><node2>the ´ character</node2></node1>', however Oracle replaced ´ immediately with ¿. I did not manage to enter ´ directly.
Almost all XML Functions return VARCAHR2 values (not NVARCAHR2), also most of XMLTYPE member functions work with CLOB (not NCLOB). If you just read and store your XML documents as XMLTYPE in your database it should be fine, however as soon you start any operations with these data sooner or later you will hit a conversion error. You should really consider to migrate your database character set, see Character Set Migration and/or Oracle Database Migration Assistant for Unicode
You can directly use the XMLType function
SELECT XMLTYPE('<?xml version="1.0" encoding="UTF-8"?>'
||TO_CLOB ('<node1><node2>the ´ character</node2></node1>')) myxml
FROM dual;

To_CHAR result cannot go in VARCHAR2

There is somthing strange I wouls like to understand
let's say we have this code :
DECLARE
a varchar2(6);
BEGIN
a := '000001';
END;
This works good. No problem
Now we have this :
DECLARE
a varchar2(6);
BEGIN
a := TO_CHAR(1, '000000');
END;
It does not work:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
why?
A problem with nls_language maybe? what problem? what solution?
Thank you
The TO_CHAR result has a space at the beginning, making its total length 7 characters. The space is reserved for a minus sign if the number is negative. If you try this query you'll see:
SELECT '[' || TO_CHAR(1, '000000') || ']' FROM DUAL;
The result is:
[ 000001]
^ space for a minus sign
To get the result without the space, use the FM modifier:
a := TO_CHAR(1, 'FM000000');
Or, just use `LTRIM' :
SQL> set serveroutput on;
SQL> DECLARE
2 a VARCHAR2(6);
3 BEGIN
4 A := LTRIM(TO_CHAR(1, '000000'));
5 dbms_output.put_line(a);
6 END;
7 /
000001
PL/SQL procedure successfully completed.
SQL>
Note that a leading blank only appears before a positive number with no sign formatting. No leading blank appears before a negative number, or before any signed number, regardless of the placement of the sign.
The “FM” format to override the default leading blank for unsigned positive numbers

DBMS_LOB.SUBSTR() throwing "character string buffer too small" error

Does oracle have a method to get substring based on number of bytes from a CLOB field?
select DBMS_LOB.SUBSTR(a.COMMENTS, 3998, 1)
FROM FOO;
I am getting error:
"ORA-06502: PL/SQL: numeric or value error: character string buffer
too small"
.
The problem was in special characters. Each new special character takes 8 bytes so when I reduce the string limit to 3992 then it works.
DBMS_LOB.SUBSTR(a.COMMENTS, 3992, 1) works.
For testing purpose I put many special characters and again it throws same error.
Does oracle have any method which finds substring based on number of bytes than number of characters?
Actually, we are fetching data from a table and need to display on UI with a limitation of 4000 characters. So, we want to fetch first 4000 characters only. As, a character size is 1 byte, we can accomodate 4000 bytes. So, if we use DBMS_LOB.CONVERTTOBLOB, we may not be able to display properly the characters string fetched. Can we convert it back it charater string somehow?
I used the old SUBSTR function successfully, which works for the clob type as well. In this case, it's SUBSTR(a.COMMENTS, 1, 3992)
It's an old problem, but I did not find any solution to it, so I will post my approach to find solution for this problem, just in case someone needs to handle this...
My task was to retrieve "the most" characters from CLOB..
DBMS_LOB.SUBSTR nor SUBSTR will achieve the correct result...
In case of using DBMS_LOB.SUBSTR I keep getting ORA-12801 with ORA-06502
Using SUBSTR another ORA-64203..
Suggestions say to do SUBSTR for max 1000, because maximum number of bytes for character is 4, therefore You won't get more then 4000 bytes, unfortunately that's not good enough for me, because You might receive only 2000 bytes this way, in case that certain row does not contain multi-byte characters...
My solution, which shouldn't be used for repeated queries by the way (rather for extracting and loading/storing data into VARCHAR2 column), is:
create or replace FUNCTION SUBSTR_MULTIBYTE_CLOB
(
P_DATA IN CLOB
, P_START_INDEX IN NUMBER
) RETURN VARCHAR2 AS
P_OUT VARCHAR2(4000 BYTE);
P_LENGTH NUMBER := 4000;
BEGIN
FOR loop_counter IN 1..400 LOOP
BEGIN
P_OUT := DBMS_LOB.SUBSTR(P_DATA,P_LENGTH-((loop_counter-1)*10),P_START_INDEX);
RETURN P_OUT;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -12801 OR SQLCODE = -6502 OR SQLCODE = -1401 OR SQLCODE = -1489 THEN
NULL; -- suppresses ORA-12801 "error signal from parallel server" or ORA-06502 exception "character string buffer too small" and some others I've got...
ELSE
RAISE;
END IF;
END;
END LOOP;
END SUBSTR_MULTIBYTE_CLOB;
If needed, You can change loop to 4000 and decrease it by one byte, I've decided to go by 10, just to make it little bit faster...
try this method::
Use Oracle function LENGTHB() to get this result. there is a way around convert CLOB to BLOB by using DBMS_LOB.CONVERTTOBLOB and use DBMS_LOB.GET_LENGTH(). this is will return no of bytes.
You can use this thread for complete answer ::
https://forums.oracle.com/forums/thread.jspa?threadID=2133623
First, try to replace all special characters from the Character string and then try to substring it to convert it to Character.
Example:- DBMS_LOB.SUBSTR(REGEXP_REPLACE(your_column, '[^0-9A-Za-z]', ''),3999,1)

Resources