Character string buffer too small error in Oracle Stored Procedure - sql

I am getting an error in an Oracle 11g stored procedure. The error is...
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
It is happening at line 31, the line that contains out_cnt_tot := 0; I'm really not sure why there is anything wrong with that line. Another programmer created this procedure and I'm really not familiar with SQL procedures. Can anyone help me figure this out?
create or replace
PROCEDURE "FIP_BANKREC_PREP"
(
in_file_date in varchar2,
in_bank_code in varchar2,
out_cnt_apx_miss_no out integer,
out_cnt_prx_miss_no out integer,
out_cnt_apx_no_mtch out integer,
out_cnt_prx_no_mtch out integer,
out_cnt_ap_dup out integer,
out_cnt_pr_dup out integer,
out_cnt_bad out integer,
out_cnt_ap_load out integer,
out_cnt_pr_load out integer,
out_cnt_ap_not_load out integer,
out_cnt_pr_not_load out integer,
out_cnt_tot out integer,
out_message out varchar2
) as
file_date date;
ap_acct_no varchar2(16);
pr_acct_no varchar2(16);
-- ------------------------------------------------------
-- begin logic
-- ------------------------------------------------------
begin
file_date := to_date(in_file_date,'yyyymmdd');
out_cnt_tot := 0; --- THE ERROR IS ON THIS LINE ---
out_message := 'Test Message';
select brec_acct_code into ap_acct_no
from MSSU.zwkfi_bankrec_accts
where brec_acct_bank = in_bank_code
and brec_acct_type = 'AP';
select brec_acct_code into pr_acct_no
from MSSU.zwkfi_bankrec_accts
where brec_acct_bank = in_bank_code
and brec_acct_type = 'PR';
// The rest of the procedure...

Simple demo of the scenario mentioned in comments:
create or replace procedure p42(out_message out varchar2) as
begin
out_message := 'Test message';
end p42;
/
If I call that with a variable that is declared big enough, it's fine. I have a 12-char variable, so assigning a 12-char value is not a problem:
declare
msg varchar2(12);
begin
p42(msg);
end;
/
anonymous block completed
But if I make a mistake and make the caller's variable too small I get the error you're seeing:
declare
msg varchar2(10);
begin
p42(msg);
end;
/
Error report:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "STACKOVERFLOW.P42", line 3
ORA-06512: at line 4
06502. 00000 - "PL/SQL: numeric or value error%s"
*Cause:
*Action:
The error stack shows both the line in the procedure that errored (line 3), and the line in the caller that triggered it (line 4). Depending on where you're calling it you might not have the whole stack, of course.
You mentioned that there would be various error messagesin the future. You need to make sure that anything that ever calls this defines the variables to be big enough to cope with any of your messages. If they were stored in a table you could semi-automate that, otherwise it'll be a manual code review check.
OK, saw your c# comment after posting this. It looks like you're calling this constructor; that doesn't say what default size it gets, but not unreasonable to think it might be 1. So you need to call this constructor instead to specify the size explicitly:
OracleParameter(String, OracleType, Int32)
Initializes a new instance of the OracleParameter class that uses the parameter name,
data type, and length.
... something like:
OracleParameter prm15 = new OracleParameter("out_str_message",
OracleDbType.Varchar2, 80);
Unless there's a way to reset the size after creation, which I can't see. (Not something I've ever used!).

Related

How to execute function with parameter in PostgreSQL using anonymous code block in FireDAC?

My database (PostgreSQL 11) has a function that must be called to execute an action. The return is not important, as long as there's no error. This function has arguments that must be passed as parameters.
I cannot directly use TFDConnection.ExecSQL because I use parameters of type array that is not supported by the method (to my knowledge). So, I use TFDQuery.ExecSQL like this:
msql1: = 'DO $$ BEGIN PERFORM DoIt (:id,:items); END $$; ';
{... create FDQuery (fq), set connection, set SQL.Text to msql1}
fq.Params.ParamByName ('id'). AsInteger: = 1;
{$ REGION 'items'}
fq.ParamByName ('items'). DataType: = ftArray;
fq.ParamByName ('items'). ArrayType: = atTable; // Must be atTable, not atArray
if length (items)> 0 then
begin
fq.ParamByName ('items'). ArraySize: = length (items);
for it: = 0 to length (items) -1 do
fq.ParamByName ('items'). AsIntegers [it]: = items [it];
end;
fq.ExecSQL;
{$ ENDREGION}
When executing the above code, the error above message raises
"Parameter 'id' not found".
After some research that suggested using fq.Params.ParamByName I was also unsuccessful.
However, if you change the way the function is called to
select DoIt (:id,:items); and obviously replacing the execution with fq.Open works perfectly.
Is it possible to execute a PL / pgSQL block that contains parameters in the function called by this block using TFDConnection / TFDQuery?
PS: I'm using Delphi Rio 10.3.3
When you assign a value to TFDQuery.SQL, FireDAC does some pre-processing of the SQL based on various options. ResourceOptions.CreateParams option controls whether parameters should be processed. This is enabled by default.
The preprocessor recognizes string literals in your SQL and doesn't try to look for the parameters in them. You used dollar quoted string constant and that's why FireDAC doesn't recognize parameters in it. Even if you add parameter manually I think that FireDAC would not bind the value.
With that said, the proper way to execute stored procedures/function is to use TFDStoredProc. You just assign StoredProcName and call its Prepare method which will retrieve procedure's metadata (parameters) from database so you don't need to set ArrayType or DataType of the parameter.
In your code you set the DataType to ftArray which is wrong, because in case of array parameter it should be set to array's element type. Anyway, by setting fq.ParamByName ('items').AsIntegers you effectively set parameter's DataType to ftInteger. All you need to do is to set ArraySize
Here's what you should do instead:
procedure DoIt(Connection: TFDConnection; ID: Integer; const Items: TArray<Integer>);
var
StoredProc: TFDStoredProc;
ParamItems: TFDParam;
Index: Integer;
begin
StoredProc := TFDStoredProc.Create(nil);
try
StoredProc.Connection := Connection;
StoredProc.StoredProcName := 'DoIt';
StoredProc.Prepare;
StoredProc.Params.ParamByName('id').AsInteger := ID;
if Length(Items) > 0 then
begin
ParamItems := StoredProc.Params.ParamByName('items');
ParamItems.ArraySize := Length(Items);
for Index := Low(Items) to High(Items) do
ParamItems.AsIntegers[Index] := Items[Index];
end;
StoredProc.ExecProc;
finally
StoredProc.Free;
end;
end;
Alternatively you can use ExecFunc to get the result of stored function.

How to call a user defined function using the 'CALL' statement?

I was going thru the Oracle documentation for the CALL statement. It is indicated there that the CALL statement can be used to call a user defined function by using INTO (not a function inside a package). I've tried loads of combinations but can't seem to get the right one. Can someone give me an example on how to do this? Thanks.
EDIT:
I've tried the example below in SQL Developer but i am getting an error.
variable x number;
call f(10) into :x;
I'm getting a bang in line 2 and the error:
SQL Error: ORA-01008: not all variables bound<br>
01008. 00000 - "not all variables bound"
From the Oracle documentation:
VARIABLE x VARCHAR2(25);
CALL warehouse_typ(456, 'Warehouse 456', 2236).ret_name()
INTO :x;
Another example:
create function f(n number) return number is
begin
return n * 2;
end;
SQL> variable x number;
SQL> call f(10) into :x;
Call completed.
SQL> print x;
X
----------
20

ORACLE SQL Method Produces Warning

I'm having trouble getting the following member method to compile (count_single_buses). Would appreciate any advice on what might be wrong syntactically with my code.
CREATE OR REPLACE TYPE BodyModel2_Type AS OBJECT(
ModelID INTEGER,
ModelName VARCHAR2(45),
FloorType VARCHAR2(45),
Manufacturer VARCHAR2(45),
Length NUMBER(8,2),
Width NUMBER(8,2),
NoOfAxles INTEGER,
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN INTEGER);
/
CREATE OR REPLACE TYPE BODY BodyModel2_Type AS
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN INTEGER IS
N INTEGER;
BEGIN
N := (SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S
WHERE S.BODYMODELREF = ModelID);
RETURN N;
END count_single_buses;
END;
--EDIT--
Thanks to #Ravi, I managed to solve the issue my correcting my SQL syntax and setting the resultset to a NUMBER, instead of INTEGER.
CREATE OR REPLACE TYPE BODY BodyModel_Type AS
MEMBER FUNCTION count_single_buses(thisModelID INTEGER) RETURN NUMBER IS
NUM NUMBER;
BEGIN
SELECT COUNT(S.BODYMODELREF) INTO NUM FROM SINGLEDECKBUS_TABLE S WHERE S.BODYMODELREF.MODELID = thisModelID;
RETURN NUM;
END count_single_buses;
END;
/
Still not sure why #Ravi's exact code still produced the warning, and thought that resultset when returning a count value could go into an integer. At any rate, the code works now. Thanks all.
Your BodyModel2_Type Type definition looks okay. However, the body definition is syntactically incorrect.
You cannot define a SQL statement directly to a variable, thus making this statement wrong.
N := (SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S
WHERE S.BODYMODELREF = ModelID);
You will have to use Select... into statement in order to assign the result set of your SQL query into a variable. So, the right syntax should look like this
SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S INTO N
WHERE S.BODYMODELREF = ModelID
AFAIK you don't have END the Type followed by the Type name like this END count_single_buses. It'll produce an error. So, overall your Type body specification should look like this:
CREATE OR REPLACE TYPE BODY BodyModel2_Type AS
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN NUMBER IS
N NUMBER;
BEGIN
SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S INTO N
WHERE S.BODYMODELREF = ModelID;
RETURN (N);
END;
END;
/
I'm writing this off without any live environment available right now so please let me know if you come across any error in the above code.
Cheers.

Issue with dynamic execute immediate query

I have a code in my procedure that looks like this. But when i execute this code, i get the error as mentioned below.
The Error report that i got is:
Error report -
ORA-06553: PLS-306: wrong number or types of arguments in call to 'OGC_Y'
ORA-06512: at line 20
06553. 00000 - "PLS-%s: %s"
*Cause:
*Action:
The error has something to do with primary_flag = "Y" <-- this. How else can i write primary_flag = 'Y' inside a string?
The dynamic query is required in my case.
MY CODE IS:
DECLARE
p_assignee_id NUMBER := 10153;
time_stamp timestamp := '12-DEC-2011';
create_task_view_sql VARCHAR2(4000);
BEGIN
create_task_view_sql:=
'select unique cp.sub_last_name
from cs_sr_contact_points_v cp
where cp.incident_id = 55500
and cp.contact_phone is not null
and primary_flag = "Y"';
dbms_output.put_line(create_task_view_sql);
execute immediate create_task_view_sql;
END;
To embed quoted string in a quoted string, use two single quotes:
'...and primary_flag=''Y''';
Or you can use the newer q' syntax to avoid doubling up the embedded quotes:
q'[...and primary_flag='Y']';

PLS-00306: wrong number or types of arguments in call to existing stored procedure

I'm using an existing stored procedure in my C code.
The stored procedure in question has been compiled and is proven to work without any errors. However, when I use the same in my C code, its failing with the above error.
The Store procedure definition looks like :
CREATE OR REPLACE FUNCTION SP(
srq_id integer ,
unid IN SPkg.arr_parmid,
parm_typ IN SPkg.arr_parm_typ,
parm_lbl IN SPkg.arr_parm_lbl,
parm_vlu IN SPkg.arr_parm_vlu,
commit_flag INTEGER DEFAULT 1,
vlu_hint IN SPkg.arr_vlu_hint,
create_flag INTEGER DEFAULT 0)
RETURN INTEGER
Type definitions
TYPE arr_parm_typ IS TABLE OF char INDEX BY BINARY_INTEGER;
TYPE arr_parmid IS TABLE OF tbl_parm.UNID%TYPE INDEX BY BINARY_INTEGER;
TYPE arr_parm_lbl IS TABLE OF tbl_parm.PARM_LBL%TYPE INDEX BY BINARY_INTEGER;
TYPE arr_parm_vlu IS TABLE OF tbl_parm.PARM_VLU%TYPE INDEX BY BINARY_INTEGER;
TYPE arr_vlu_hint IS TABLE OF tbl_parm.VLU_HINT%TYPE INDEX BY BINARY_INTEGER;
My C code looks like :
typedef struct param
{
char lbl[30][81];
char vlu[30][256];
char typ[30];
ub8 seq_no[30];
char vlu_hint[30];
}PARAM;
ub8 srqid;
int commit_flag;
int create_flag;
PARAM p_array;
The way I invoke the stored procedure:
char command[250] = "begin :retval := SSP_srq_parm_all(:srq_id,:unid,:parm_typ,:parm_lbl,:parm_vlu,:commit_flag,:vlu_hint,:create_flag); end;";
OCIStmtPrepare2((OCISvcCtx *)svchp, (OCIStmt **)&(stmthp),
(OCIError *)errhp, (OraText *)command,
(ub4)strlen((char*)command), (OraText *)NULL, (ub4)0,
OCI_NTV_SYNTAX, OCI_DEFAULT);
//..... calls to OCIBindByName & OCIBindArrayOfStruct here..........
status= OCIStmtExecute(svchp, stmthp,errhp, (ub4)1, (ub4) 0,(CONST OCISnapshot *) NULL,(OCISnapshot *) NULL, OCI_DEFAULT);
OCIStmtExecute() fails with the above error.
Please let me know if you need to know how the 'bind' calls look like. I dint paste them right away because the code chunk is pretty big.
Can someone please help?
Firstly, a function is not a procedure - but that isn't the problem. Since you're using the correct number of parameters, the only thing that it could be is that the implementation of the custom types you're using don't match between Oracle and C. How are they defined in PL/SQL?