PLSQL dynamically truncate string to varchar2 column size - oracle

I'm looking for a way to dynamically truncate a string to the max size of a varchar2 column.
See example below.
Table definition
CREATE TABLE some_table
(
log varchar2(50)
);
Code
DECLARE
v_str some_table.log%TYPE;
BEGIN
v_str := SUBSTR('a text longer than 50 chars ...', 1, 50 /*HOW TO GET 50 DYNAMICALLY HERE ?*/ );
END;
Any ideas?

select char_length
from user_tab_columns
where table_name = 'SOME_TABLE'
and column_name = 'LOG';
would return a value of 50 (the length of the log column in the some_table table)

Getting (and caching) the column length is probably the way to go, but just for fun and riffing from an approach in this discussion:
DECLARE
v_str some_table.log%TYPE;
v_ret pls_integer;
v_len pls_integer;
BEGIN
v_ret := anydata.convertchar('-').getchar(v_str);
v_len := length(v_str);
dbms_output.put_line(v_len);
v_str := substr('a text longer than 50 chars, a text longer than 50 chars, a text longer than 50 chars...', 1, v_len);
dbms_output.put_line(v_str);
END;
/
dbms_output:
50
a text longer than 50 chars, a text longer than 50
Because it's using convertchar and getchar rather than the varchar2 versions, the result is the literal char - padded to its maximum length based on the variable's data type.
db<>fiddle
You can read more about anydata at Oracle-base or in the documentation.
You could still cache that, or work it out on exception. If you were going to do this often and performance was acceptable without caching/catching, you could possibly wrap it in a procedure:
CREATE PROCEDURE trunc_string(p_str out varchar2, p_val varchar2)
AS
v_ret pls_integer;
v_len pls_integer;
BEGIN
v_ret := anydata.convertchar('-').getchar(p_str);
v_len := length(p_str);
p_str := substr(p_val, 1, v_len);
END;
/
and then call that as:
DECLARE
v_str1 some_table.log%TYPE;
v_str2 varchar2(30);
BEGIN
trunc_string(v_str1, 'a text longer than 50 chars, a text longer than 50 chars, a text longer than 50 chars...');
dbms_output.put_line(v_str1);
trunc_string(v_str2, 'a text longer than 30 chars, a text longer than 30 chars, a text longer than 30 chars...');
dbms_output.put_line(v_str2);
END;
/
dbms_output:
a text longer than 50 chars, a text longer than 50
a text longer than 30 chars, a
db<>fiddle
But that's probably overkill... though that could be said about using anydata at all.

Related

ORA-06502 with ORA-06512

I have a procedure in which I'm trying to write a source code (1290 lines) to dbms_output like this:
dbms_output.put_line(DBMS_METADATA.GET_DDL('FUNCTION', 'name', 'owner')); --MYPROC, line 6
I'm getting :
ORA-06502: PL/SQL: numeric or value error
ORA-06512: in "MYPROC", line 6
.
This error occures in toad.
I can execute in editor tab of toad:
SELECT DBMS_METADATA.GET_DDL('FUNCTION', 'name', 'owner') FROM DUAL;
I mean I'm getting the source code in 'Data grid'.
Same happens when I try to store the code in a CLOB variable:
src CLOB;
...
src := DBMS_METADATA.GET_DDL('FUNCTION', 'name', 'owner') ; --MYPROC, line 6
Any clue?
From the documentation for dbms_output:
The maximum line size is 32767 bytes.
That means that you can't pass more than that in a single put_line call. You are currently passing your whole CLOB, which at 1290 lines is likely to exceed that limit. And the error you get when you do that is "ORA-06502: PL/SQL: numeric or value error", as you are seeing.
You can split your CLOB into smaller chunks, and as it is already multiple lines it makes sense to make each chunk a single line from the DDL. You can do that by looking for newline characters, extracting all the text up to the next one, and printing that. You need a few variables to keep track of where you are. Something like this should work for you:
declare
src clob;
src_length pls_integer;
pos pls_integer := 1;
buffer varchar2(32767);
amount pls_integer := 32767;
begin
src := dbms_metadata.get_ddl('FUNCTION', 'TEST_FUNCTION_1', user);
src_length := dbms_lob.getlength(src);
while pos < src_length loop
-- read to next newline if there is one, rest of CLOB if not
if dbms_lob.instr(src, chr(10), pos) > 0 then
-- see how many charcaters there are until next newline
amount := dbms_lob.instr(src, chr(10), pos) - pos;
-- if there are any, read them into the buffer; otherwise clear it
if amount > 0 then
dbms_lob.read(src, amount, pos, buffer);
else
buffer := null;
end if;
pos := pos + amount + 1; -- skip newline character
else
-- no newline so read everything that is left
amount := 32767;
dbms_lob.read(src, amount, pos, buffer);
pos := pos + amount;
end if;
dbms_output.put_line(buffer);
end loop;
end;
/
It won't work if you have a single line (without or without a newline at the end) that is more than 32k, which hopefully won't be an issue with DDL. (You could sort of handle it, but doing so would inject additional newlines, which wouldn't be good either.)
What you are saying can't be true. DBMS_OUTPUT.PUT_LINE can't be used at SQL level, it belongs to PL/SQL.
What is MYPROC and what does it contain at line #6?
Which "editor" is "I can execute in editor"?
Numeric or value error is usually related to the fact that you're trying to store "large" values into a "small" variable:
SQL> declare
2 l_var varchar2(2);
3 begin
4 l_var := 'ABC';
5 end;
6 /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 4
SQL>
which is what - I presume - you did.
Another cause is wrongly declared variable, e.g.
SQL> declare
2 l_var number;
3 begin
4 l_var := 'A';
5 end;
6 /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 4
SQL>
I'll try to guess what you might be doing:
SQL> set serveroutput on
SQL> DECLARE
2 src CLOB;
3 BEGIN
4 src := DBMS_METADATA.GET_DDL ('PACKAGE', 'MY_PKG', 'SCOTT');
5 DBMS_OUTPUT.put_line ('len = ' || DBMS_LOB.getlength (src));
6 END;
7 /
len = 67239
PL/SQL procedure successfully completed.
SQL>
As you can see, it works OK for me. Package isn't that small (see its length), so - can't really tell what you did wrong. I'd suggest you to do exactly as I did - copy/paste code I posted above (those 7 lines), fix information (function, its name, owner) and post the result by editing the original question, not as a comment.

How to execute Oracle procedure with clob parameter in?

I have a procedure
create or replace PROCEDURE PROC_PROJPREL_TEMPL_SERV_MAT(
P_TABELA IN VARCHAR2,
P_COLUNAS IN VARCHAR2,
P_DADOS IN CLOB,
O_CODIGO OUT NUMBER,
O_MENSAGEM OUT VARCHAR2
) IS
BEGIN
o_codigo := 0;
o_mensagem := '';
-- no implementation coded yet
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20101, 'erro ao executar procedure ');
END PROC_PROJPREL_TEMPL_SERV_MAT;
And I need to execute this in SQL Developer.
I tried using anonymous block
declare
i_tabela varchar2(30);
i_colunas varchar2(4000);
i_dados clob;
o_codigo number;
o_mensagem varchar2(4000);
begin
i_tabela := 'table_name'; -- max 30 characters
i_colunas := 'columns_names'; -- less 4000 characters
i_dados := '45000 characters';
proc_projprel_templ_serv_mat(i_tabela, i_colunas, i_dados, o_codigo, o_mensagem);
end;
But it returns an error "string literal too long"
and I tried using "call" command too.
call proc_projprel_templ_serv_mat('table_name', 'columns_names', &DATAS);
But it returns an error ORA-00972 identifier is too long, Cause: An identifier with more than 30 characters was specified, Action: Specify at most 30 characters.
Somebody can help me?
The maximum length of a string literal in PL/SQL is 32,767 characters. As the error "string literal too long" is saying, you're blowing out this limit here:
i_dados := '45000 characters';
You have to break up that string into sections up to 32,767 characters long and concatenate them together, e.g.:
i_dados := 'first 32767 characters' ||
'remaining 12233 characters';

numeric or value error: raw variable length too long ORA-06512: at "SYS.UTL_RAW"

I am facing raw variable length too long issue when select the BLOB(Stored JSON string) filed value from the table.
Query:
select utl_raw.cast_to_varchar2(dbms_lob.substr(TRANSACTION_DATA)) from PS_ISA_INB_PAYLOAD_LOG;
This is my SP i have used to insert the JSON object into BLOB Field:
create or replace PROCEDURE SDIX_TICK_LOG
(
ORGANIZATIONNAME IN VARCHAR2
, TRANSACTION_TYPE IN VARCHAR2
, TRANSACTION_DATA IN BLOB
, TRANSACTION_STATUS IN VARCHAR2
) AS
l_r RAW(32767);
l_blob blob;
l_clob clob :='"ItemMasterTransfer":[{"ORACLE_UNIQUE_REC_ID":"123assd4434","CUSTOMER_ID":"PMC","ORGANIZATION_CODE":"BMftrdsM","ITEM":"696738","INVENTORY_ITEM_ID":"0000000000000000000000000000546","ORGANIZATION_ID":" ","SUBINVENTORY_CODE":"000000000000000000","LOCATOR_ID":" ","ISA_MATERIAL_GROUP":" ","TAX_GROUP":" ","CATEGORY_ID":"1956","NOUN":" ","MODIFIER":" ","MANUFACTURER_ID":" ","MFG_ITEM_ID":" ","UNIT_OF_MEASURE":"BOX","ITEM_TYPE":"P","STOCK_ENABLED_FLAG":"Y","INVENTORY_ITEM_STATUS_CODE":"A","LIST_PRICE_PER_UNIT":"0","FULL_LEAD_TIME":"0","MAX_MINMAX_QUANTITY":"10","MIN_MINMAX_QUANTITY":"10","SAFETY_LEVEL":"0","REPLENISH_TO_ORDER_FLAG":"N","UTILIZ_CD":"","CURRENCY_CD":"USD","DESCRIPTION":"","ATTRIBUTE1":" ","ATTRIBUTE2":" ","ATTRIBUTE3":" ","ATTRIBUTE4":" ","ATTRIBUTE5":" ","ATTRIBUTE6":" ","ATTRIBUTE7":" ","ATTRIBUTE8":" ","ATTRIBUTE9":" ","ATTRIBUTE10":" ","TRANSACTION_STATUS":" ","TRANS_STATUS_DESCRIPTION":" "},{"ORACLE_UNIQUE_REC_ID":"123assd4434","CUSTOMER_ID":"PMC","ORGANIZATION_CODE":"BMftrdsM","ITEM":"696738","INVENTORY_ITEM_ID":"0000000000000000000000000000546","ORGANIZATION_ID":" ","SUBINVENTORY_CODE":"000000000000000000","LOCATOR_ID":" ","ISA_MATERIAL_GROUP":" ","TAX_GROUP":" ","CATEGORY_ID":"1956","NOUN":" ","MODIFIER":" ","MANUFACTURER_ID":" ","MFG_ITEM_ID":" ","UNIT_OF_MEASURE":"BOX","ITEM_TYPE":"P","STOCK_ENABLED_FLAG":"Y","INVENTORY_ITEM_STATUS_CODE":"A","LIST_PRICE_PER_UNIT":"0","FULL_LEAD_TIME":"0","MAX_MINMAX_QUANTITY":"10","MIN_MINMAX_QUANTITY":"10","SAFETY_LEVEL":"0","REPLENISH_TO_ORDER_FLAG":"N","UTILIZ_CD":"","CURRENCY_CD":"USD","DESCRIPTION":"","ATTRIBUTE1":" ","ATTRIBUTE2":" ","ATTRIBUTE3":" ","ATTRIBUTE4":" ","ATTRIBUTE5":" ","ATTRIBUTE6":" ","ATTRIBUTE7":" ","ATTRIBUTE8":" ","ATTRIBUTE9":" ","ATTRIBUTE10":" ","TRANSACTION_STATUS":" ","TRANS_STATUS_DESCRIPTION":" "},{"ORACLE_UNIQUE_REC_ID":"123assd4434","CUSTOMER_ID":"PMC","ORGANIZATION_CODE":"BMftrdsM","ITEM":"696738","INVENTORY_ITEM_ID":"0000000000000000000000000000546","ORGANIZATION_ID":" ","SUBINVENTORY_CODE":"000000000000000000","LOCATOR_ID":" ","ISA_MATERIAL_GROUP":" ","TAX_GROUP":" ","CATEGORY_ID":"1956","NOUN":" ","MODIFIER":" ","MANUFACTURER_ID":" ","MFG_ITEM_ID":" ","UNIT_OF_MEASURE":"BOX","ITEM_TYPE":"P","STOCK_ENABLED_FLAG":"Y","INVENTORY_ITEM_STATUS_CODE":"A","LIST_PRICE_PER_UNIT":"0","FULL_LEAD_TIME":"0","MAX_MINMAX_QUANTITY":"10","MIN_MINMAX_QUANTITY":"10","SAFETY_LEVEL":"0","REPLENISH_TO_ORDER_FLAG":"N","UTILIZ_CD":"","CURRENCY_CD":"USD","DESCRIPTION":"","ATTRIBUTE1":" ","ATTRIBUTE2":" ","ATTRIBUTE3":" ","ATTRIBUTE4":" ","ATTRIBUTE5":" ","ATTRIBUTE6":" ","ATTRIBUTE7":" ","ATTRIBUTE8":" ","ATTRIBUTE9":" ","ATTRIBUTE10":" ","TRANSACTION_STATUS":" ","TRANS_STATUS_DESCRIPTION":" "}],"Organization":"PMC Biogenix","SharedSecret":"sTc1QowIu5Iy1Qt8iilnmQ==","TimeStamp":"09/28/2018 00:19:21","RowsSent":"1"}';
l_amt integer := dbms_lob.lobmaxsize;
l_dest_offset integer := 1;
l_src_offset integer := 1;
l_csid integer := dbms_lob.default_csid;
l_ctx integer := dbms_lob.default_lang_ctx;
l_warn integer;
BEGIN
dbms_lob.createTemporary( l_blob, false );
dbms_lob.convertToBlob( l_blob,
l_clob,
l_amt,
l_dest_offset,
l_src_offset,
l_csid,
l_ctx,
l_warn );
INSERT INTO PS_ISA_INB_PAYLOAD_LOG Values(ORGANIZATIONNAME,TRANSACTION_TYPE,l_blob,SYSDATE,TRANSACTION_STATUS);
END SDIX_TICK_LOG;
Your problem lies here: DBMS_LOB.SUBSTR()
DBMS_LOB is using VARCHAR2 internally, and VARCHAR2 has limit of 2000 chars. Your blob has the size of 2829 chars, therefore it is too long to be processed by DBMS_LOB.SUBSTR() at once.
You can test this by these commands:
Take only first 2000 chars from BLOB:
select utl_raw.cast_to_varchar2(dbms_lob.substr(TRANSACTION_DATA), 2000, 1) from PS_ISA_INB_PAYLOAD_LOG;
OK.
Take 2001 chars from BLOB:
select utl_raw.cast_to_varchar2(dbms_lob.substr(TRANSACTION_DATA, 2001, 1)) from PS_ISA_INB_PAYLOAD_LOG;
Error report -
SQL Error:
ORA-06502: PL/SQL: numeric or value error: raw variable length too long
ORA-06512: at line 1
06502.00000 - "PL/SQL: numeric or value error%s"
Is that possible to select entire BLOB Field value?
Basically, no. You'll need a PL/SQL function similar to the one you've already described, except this time, it will go BLOB-to-CLOB. You can call that from SQL, if it returns a CLOB or a VARCHAR2(4000).
As a side note, I question why you're taking JSON, which is character data, and storing it as a BLOB, then wanting to get character data back. Why not just store it as a CLOB in the first place?

How to get the declared size of a varchar2 in oracle database

Trying to get the size of a defined variable in Oracle. I may need to use a number when declaring the size of a varchar2 but would rather not have to keep track of an extra variable or number.
example pseudo code:
declare
myvar varchar(42) := 'a';
begin
/* I know the length is length(myvar) = 1. */
/* but how do I get 42? */
/* What is the max defined size of this variable */
declared_size_of(myvar);
end
The reason I need this is to lpad the length of the string to the declared size so it doesn't generate an exception.
As #Justin said in his comments, you don't have to explicitly blank pad the string if you use CHAR data type. Oracle would blank-pad the value to it's maximum size.
From documentation,
If the data type of the receiver is CHAR, PL/SQL blank-pads the value
to the maximum size. Information about trailing blanks in the original
value is lost.
For example,
SQL> SET serveroutput ON
SQL> DECLARE
2 myvar CHAR(42);
3 BEGIN
4 myvar:='a';
5 dbms_output.put_line(LENGTH(myvar));
6 END;
7 /
42
PL/SQL procedure successfully completed.
SQL>
Brute Force technique using exception handling which is probably very inefficient:
DECLARE
myvar varchar2(42) := 'a'; /* using varchar */
v_size number := null;
x varchar(4000) := '';
v_length number := 0;
BEGIN
begin
v_length := length(myvar);
x := myvar;
FOR i in v_length..8001 LOOP
myvar := myvar || ' '; /* add one space at a time until it causes an exception */
End Loop;
EXCEPTION
-- WHEN NO_DATA_FOUND THEN
WHEN OTHERS THEN
v_length := length(myvar);
end;
dbms_output.put_line('Declared size is varchar('||v_length
||') and length(myvar) is '||length(trim(myvar)));
END;
To fetch the max. of a column input, you simply could do:
SELECT MAX(LENGTH(Column))
FROM TableA;

Convert Varchar2 to Char array in Oracle

I have a varchar2 field and want to split it to array of chars
Like 'ABCDEF' --> 'A' 'B' 'C' 'D' 'E'
How can i convert my Field Values to chars array?
If you actually mean a PL/SQL collection of characters, you could do something like
SQL> ed
Wrote file afiedt.buf
1 declare
2 type char_arr is table of char(1) index by pls_integer;
3 l_str varchar2(100) := 'ABCDEF';
4 l_arr char_arr;
5 begin
6 for i in 1 .. length(l_str)
7 loop
8 l_arr(i) := substr( l_str, i, 1 );
9 end loop;
10 dbms_output.put_line( l_arr.count );
11* end;
SQL> /
6
PL/SQL procedure successfully completed.
Without understanding the business requirements, though, I would tend to be very suspicious. When you find yourself breaking apart strings in PL/SQL, that almost always implies that you have stored data in a non-atomic form and need to address the data model issue.

Resources