SQL: How to get substring from function output in sql? - sql

IN one of My Application. We have one query, where actully we are calling a function and function returns o/p as String. Any one have idea how to get substring from function returned o/p?
I am using like this
select substr(myfunction(),0,4000) from dual.
I am getting below issue.
ORA-06502: PL/SQL: numeric or value error: character string buffer too
small.
Please help me . Thanks in Advance.

Since you're applying a substr, presumably the value being returned by your function is greater than 4000 characters. If that's the case then you will get this error from SQL, you can't avoid it. It's trying to assign the long string value to an (implicit) SQL-level varchar2, which of course cannot be more than 4000 characters either, before passing that to the substr function.
You will have to add the substr to the return from your function, or if it is sometimes called from somewhere that can handle the long values, you can have a wrapper function that only returns the first 4000 characters - so you can have the appropriate value as needed.
To demonstrate with a dummy function to (inefficiently!) create a large string:
create or replace function myfunction(strlen number) return varchar2 is
str varchar2(32767) := 'X';
begin
while length(str) < least(strlen, 32767) loop
str := str || 'X';
end loop;
return str;
end myfunction;
/
This is fine because the function's output doesn't exceed the SQL varchar2 size:
select length(substr(myfunction(4000),0,4000)) from dual;
LENGTH(SUBSTR(MYFUNCTION(4000),0,4000))
---------------------------------------
4000
But this gets your error because the function's output is too long:
select length(substr(myfunction(4001),0,4000)) from dual;
SQL Error: ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "STACKOVERFLOW.MYFUNCTION", line 7
06502. 00000 - "PL/SQL: numeric or value error%s"
With a simple wrapper function as a workaround:
create or replace function trunc_myfunction(strlen number) return varchar2 is
begin
return substr(myfunction(strlen), 0, 4000);
end;
/
select length(substr(trunc_myfunction(4000),0,4000)) from dual;
LENGTH(SUBSTR(TRUNC_MYFUNCTION(4000),0,4000))
---------------------------------------------
4000
select length(substr(trunc_myfunction(4001),0,4000)) from dual;
LENGTH(SUBSTR(TRUNC_MYFUNCTION(4001),0,4000))
---------------------------------------------
4000
select length(substr(trunc_myfunction(32767),0,4000)) from dual;
LENGTH(SUBSTR(TRUNC_MYFUNCTION(32767),0,4000))
----------------------------------------------
4000

Most probably you are trying to store more characters than is allowed in one of the variables that you use in your function. See: PL/SQL: numeric or value error: character string buffer too small %ROWTYPE
Simplest example:
DECLARE
v_varchar2_test VARCHAR2(5);
BEGIN
v_varchar2_test := '123456';
END;
And the error is, like in your case, ORA-06502.

Make sure that the value returned by the function is of the correct type;
I have just tried it like this:
create or replace
FUNCTION MONTH
(DATA IN DATE)
RETURN VARCHAR IS
BEGIN
RETURN TO_CHAR(DATA, 'MM');
END;
and the call:
SELECT substr(MONTH(SYSDATE),0,1) FROM DUAL;
and it worked.

Related

Is it possible to have constant function parameters in SQL?

Is it possible to have constant function parameters in SQL to make sure the values are not changed later?
Something like this doesn't work:
function my_func(
first_param constant varchar2
, second_param constant varchar2
) return varchar2
is
... -- Rest
That is not necessary as you cannot redefine an IN parameter. For example:
CREATE FUNCTION does_not_work(
a IN NUMBER,
b IN NUMBER
) RETURN NUMBER
IS
BEGIN
IF a < 2 THEN
a := 2;
END IF;
RETURN GREATEST( a, b );
END;
/
(Note: the IN keyword is optional and the default parameter direction; you would get the same error if you declared the signature without the IN keywords.)
Gives:
ORA-24344: success with compilation error
If you look at the errors:
SELECT * FROM USER_ERRORS;
Outputs:
NAME
TYPE
SEQUENCE
LINE
POSITION
TEXT
ATTRIBUTE
MESSAGE_NUMBER
DOES_NOT_WORK
FUNCTION
1
8
5
PLS-00363: expression 'A' cannot be used as an assignment target
ERROR
363
DOES_NOT_WORK
FUNCTION
2
8
5
PL/SQL: Statement ignored
ERROR
0
This tells you that you can't use an IN parameter as an assignment target.

Assigning a string value to a variable of type NUMBER in pl/sql procedure not throwing any error

I have started working on pl/sql recently. So still not very clear with the concepts. I am a writing a Stored procedure which has some input values.
using thses input values as keys,i create a cursor.
After this i iterate through the cursor output , and call insert statement for each row fetched.
before calling the insert, i am doing few data manipulations.
Splitting a String '4564:0:75556' considering : as the delimiter.
For this i am using SUBSTR and INSTR functions
My actual question starts here.
The output of SUBSTR will be a string value. i am assigning this value to a variable of type NUMBER which i declared in the proc.
I was expecting an error while compiling this proc or atleast when i test it.
Strangely i didnt get any error, the proc works fine.
is this the expected behaviour?
Can we assign a string value to variable of type NUMBER?
in my proc i am assignin output of SUBSTR to variables d_c1_subscr_no, d_c1_account_no, d_c1_subscr_no_resets NUMBER
My Stored proc:
CREATE OR REPLACE PROCEDURE P4_UPDATE_BILL_PERIOD_BULK(oldperiod IN VARCHAR,
newperiod IN VARCHAR,
accountno IN VARCHAR) IS
d_c1_subscr_no NUMBER;
d_c1_account_no NUMBER;
d_c1_subscr_no_resets NUMBER;
d_first_occ NUMBER;
d_second_occ NUMBER;
CURSOR c1_active_subs IS
select ciem.external_id as occ_ext_id,
ciem.subscr_no as fx_subscr_no,
ciem.subscr_no_resets as fx_subscr_no_resets
from customer_id_equip_map ciem, service s
where ciem.subscr_no = s.subscr_no
AND s.parent_account_no = to_number(accountno)
AND ciem.subscr_no_resets = s.subscr_no_resets
AND ciem.external_id_type = 6
AND ciem.active_date < SYSDATE
AND (ciem.inactive_date is null or ciem.inactive_date > SYSDATE);
d_subscriber c1_active_subs%ROWTYPE;
BEGIN
OPEN c1_active_subs;
LOOP
FETCH c1_active_subs
into d_subscriber;
EXIT WHEN c1_active_subs%NOTFOUND;
IF c1_active_subs%FOUND THEN
d_first_occ := INSTR(d_subscriber.occ_ext_id, ':', 1, 1);
d_second_occ := INSTR(d_subscriber.occ_ext_id, ':', 1, 2);
d_c1_subscr_no := SUBSTR(d_subscriber.occ_ext_id,
1,
d_first_occ - 1);
d_c1_subscr_no_resets := SUBSTR(d_subscriber.occ_ext_id,
d_first_occ + 1,
d_second_occ - d_first_occ - 1);
d_c1_account_no := SUBSTR(d_subscriber.occ_ext_id,
d_second_occ + 1);
INSERT INTO P4_BULK_DATA
(BATCH_ID, STATUS, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8)
VALUES
(10300,
0,
accountno,
d_subscriber.fx_subscr_no,
d_subscriber.fx_subscr_no_resets,
d_c1_account_no,
d_c1_subscr_no,
d_c1_subscr_no_resets,
oldperiod,
newperiod);
END IF;
END LOOP;
CLOSE c1_active_subs;
END;
Oracle database will normally conduct implicit conversions between many data types. As clarified in the link provided in the comment, Oracle implicitly converts from VARCHAR2 to NUMBER and vise versa. That said, it is always not recommended approach to rely on Oracle implicit conversions. It is always better to use the rich library of conversion functions provided by Oracle. For example:
Using the concatenation operator (||) to concatenate a string and an arithmetic expression can produce an error, which you can prevent by using the TO_CHAR function to convert the arithmetic expression to a string before concatenation
Relying on language settings in the database for the format of a DATE value can produce unexpected results, which you can prevent by using the TO_CHAR function and specifying the format that you want

Insert character to a number datatype column

Note: I cant change the datatype of column
I want to store a character into a table that has column with number datatype.
The work around i found is convert the character values to ASCII and when retrieving it from the database convert it back to character.
I used couple of function ASCII and ASCIISTR but the limitation with these functions are they are converting only first character of the string.
So i used dump function
select dump('Puneet_kushwah1') from dual;
Result: Typ=96 Len=15: 80,117,110,101,101,116,95,107,117,115,104,119,97,104,49
This function is giving ASCII value of all the characters. Then i execute below query
select replace(substr((DUMP('Puneet_kushwah1')),(instr(DUMP('Puneet_kushwah1'),':')+2 )),',',' ') from dual;
Result: 80 117 110 101 101 116 95 107 117 115 104 119 97 104 49
then i used a special character to fill the space, so that i can replace it while retrieving from the database.
select replace(substr((DUMP('Puneet_kushwah1')),(instr(DUMP('Puneet_kushwah1'),':')+2 )),',','040') from dual;
Result: 80040117040110040101040101040116040950401070401170401150401040401190409704010404049
Table definition:
create table test (no number);
Then i inserted it into the table
INSERT into test SELECT replace(substr((DUMP('Puneet_kushwah1')),(instr(DUMP('Puneet_kushwah1'),':')+2 )),',','040') from dual;
Problem 1:
When i execute
select * from test;
i got
Result: 8.004011704011E82
I want to convert it into number only. Exact same what i inserted.
Problem 2:
And then when i execute select i want it to return the exact character string.
Please help i tried many functions.
Thanks in advance.
You can't get the exact string back because Oracle numbers are only stored up to 38 digits of precision.
So if you run this:
select cast(no as varchar2(100))
from test;
You'll get:
80040117040110040101040101040116040950400000000000000000000000000000000000000000000
While I advise not to proceed like this as this could be rife for errors and a possible maintenance nightmare, I do like a challenge and have been forced to do some screwy things myself in order make some vendor's bizarre way of doing things work for us so I sympathize with you if that is the case. So, for the fun of it check this out.
Convert to hex, then to a decimal and insert into the database (x_test has one NUMBER column), then select, converting back:
SQL> insert into x_test
2 select to_number(rawtohex('Puneet_kushwah1'), rpad('X', length(rawtohex('Puneet_kushwah1')), 'X')) from dual;
1 row created.
SQL> select * from x_test;
col1
----------
4.1777E+35
SQL> SELECT utl_raw.cast_to_varchar2(hextoraw(trim(to_char(col1, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'))))
2 FROM x_test;
UTL_RAW.CAST_TO_VARCHAR2(HEXTORAW(TRIM(TO_CHAR(col1,'XXXXXXXXXXXXXXXXXXXXXXXXXXXX
--------------------------------------------------------------------------------
Puneet_kushwah1
SQL>
While it's a horrible idea and a horrible data model, you could convert some strings into numbers by converting their raw representation into a number:
create or replace function string_to_number(p_string varchar2)
return number as
l_raw raw(40);
l_number number;
begin
l_raw := utl_i18n.string_to_raw(data => p_string, dst_charset => 'AL32UTF8');
l_number := to_number(rawtohex(l_raw), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
return l_number;
end;
/
And back again:
create or replace function number_to_string(p_number number)
return varchar2 as
l_raw raw(40);
l_string varchar2(20);
begin
l_raw := hextoraw(to_char(p_number, 'fmxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'));
l_string := utl_i18n.raw_to_char(data => l_raw, src_charset => 'AL32UTF8');
return l_string;
end;
/
Which you could use as:
insert into test (no) values (string_to_number('Puneet_kushwah1'));
1 row inserted.
select * from test;
NO
---------------------------------------
417765537084927079232028220523112497
select number_to_string(no) from test;
NUMBER_TO_STRING(NO)
--------------------------------------------------------------------------------
Puneet_kushwah1
You don't really need functions, you could do the conversions in-line; but this makes what's happening a bit clearer.
But you're restricted by the precision of the number type. I think you're limited to about 20 characters, but it'll depend a bit on the actual string and its hex representation.
(I am not endorsing this approach, it's just a mildly interesting problem).

ORA-06502: PL/SQL: numeric or value error: character string buffer too small

I tried the following code different ways, like by taking out the while or the if, but when I put both together (if and while), I always get the error at the end...
undefine numero
set serveroutput on
accept numero prompt 'Type # between 100 and 999: '
declare
i number:=1;
a char(25);
b char(1);
c varchar2(10);
d number;
begin
c := &numero;
d := length(c);
b := substr(c, i, 1);
while i <= d loop
if b = '1' then
a:= a||'one ';
end if;
i := i+1;
end loop;
dbms_output.put_line('The number is '||a);
end;
/
ERROR:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 13
06502. 00000 - "PL/SQL: numeric or value error%s"
FIXED by changing how I declared the variable "a" to:
a varchar2(2000);
*Notice that here, the significant change is to use VARCHAR2 instead of CHAR (not the bigger length). According to #user272735 's answer, that's the key.
PL/SQL: numeric or value error: character string buffer too small
is due to the fact that you declare a string to be of a fixed length (say 20), and at some point in your code you assign it a value whose length exceeds what you declared.
for example:
myString VARCHAR2(20);
myString :='abcdefghijklmnopqrstuvwxyz'; --length 26
will fire such an error
CHAR is a fixed-length data type that uses as much space as possible. So a:= a||'one '; will require more space than is available. Your problem can be reduced to the following example:
declare
v_foo char(50);
begin
v_foo := 'A';
dbms_output.put_line('length of v_foo(A) = ' || length(v_foo));
-- next line will raise:
-- ORA-06502: PL/SQL: numeric or value error: character string buffer too small
v_foo := v_foo || 'B';
dbms_output.put_line('length of v_foo(AB) = ' || length(v_foo));
end;
/
Never use char. For rationale check the following question (read also the links):
Oracle datatype: Should I use VARCHAR2 or CHAR
This may also happen if you have a faulty or accidental equation in your csv file.
i.e - One of the cells in your csv file starts with an equals sign (=) (An excel equation) which will, in turn throw an error.
If you fix, or remove this equation by getting rid of the equals sign, it should solve the ORA-06502 error.

How to pass String values through variable in VARRAY functions in PL/SQL

How to pass String values through variable in VARRAY functions in PL/SQL.
Coding:
create or replace
PROCEDURE dynamic_query_build
(
vr_plan_sku_id IN VARCHAR2
)
IS
type plan_sku_id_array IS VARRAY(999) OF VARCHAR2(5000);
plan_sku_id plan_sku_id_array;
total integer;
vrx_plan_sku_id VARCHAR2(3000);
BEGIN
vrx_plan_sku_id:= replace(vr_plan_sku_id,',',chr(39)||','||chr(39));
vrx_plan_sku_id:=chr(39)||vrx_plan_sku_id||chr(39);
--plan_sku_id := plan_sku_id_array('Apple','Apple','Orange','Banana');
dbms_output.put_line(vrx_plan_sku_id);
plan_sku_id := plan_sku_id_array(vrx_plan_sku_id);
total := plan_sku_id.count;
dbms_output.put_line('Array count: 'total);
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END dynamic_query_build;
Execution:
set serveroutput on;
declare
vr_plan_sku_id varchar2(200) := 'Apple,Apple,Orange,Banana';
BEGIN
dynamic_query_build(vr_plan_sku_id);
END;
/
My Output:
Array count: 1
Expected Output:
Array count: 4
Explanation:
When i pass the string values like "plan_sku_id_array('Apple','Apple','Orange','Banana')" then i am getting count values is 4.. But when i pass the same string through variables then it is considering whole value as single value in the varray.
You need to substring your input parameter (based on ,) and then do something like this post (e.g. call EXTEND(); then ary(count)= value). Keep extending and adding as long as you still have some value in your input parameter
Can pass your value string like in given below way. This would convert entire comma separated string in different rows and you can run a loop and store variables in VARRAY.
SELECT trim(x.column_value.extract('e/text()')) COLUMNS
from t t, table (xmlsequence(xmltype('<e><e>' || replace(valuestring,':','</e><e>')||
'</e></e>').extract('e/e'))) x );