Error when running stored procedure : maximum number of object durations exceeded seems when variable not pass it works - oracle

I'm newbie in stored procedures and I create a stored procedure, but when I run it by user input, I get an error; but when get value to variable daynumber, it is working.
Suggetions from SQL Developer are:
*Cause: This typically happens if there is infinite recursion in the PL/SQLfunction that is being executed.
*Action: User should alter the recursion condition in order to prevent infinite recursion.
How can I solve it?
create or replace procedure P_SiteNumber_Range_D(Sitenum NUMBER) is
daynumber number;
begin
p_sitenumber_range_d(Sitenum => daynumber);
-- daynumber := 2;
for l in (select PROVINCE from v_sitenumber_D_province_range)
loop
update PM4h_db.IND_D_3102
set IND_D_3102_029 =
(select countsite from some table where l1.province=province );
end loop;
end P_SiteNumber_Range_D;
Run procedure as :
DECLARE
SITENUM NUMBER;
BEGIN
SITENUM := 3;
P_SITENUMBER_RANGE_D(
SITENUM => SITENUM
);
END;

This procedure doesn't make much sense (at least, to me).
you are passing sitenum and never do anything with it; should it be used in where clause in cursor for loop and/or update statement?
this is a procedure, and then - in line #4 of your original code - you are calling itself (which then calls itself which calls itself etc., until Oracle stops it and returns an error)
the most obvious "solution" is to remove that statement:
p_sitenumber_range_d(Sitenum => daynumber);
but that probably won't be all, because of my first objection
Furthermore, maybe you don't need the loop at all, as the whole code can be rewritten as
create or replace procedure p_sitenumber_range_d (par_sitenum in number)
is
begin
update pm4h_db.ind_d_3102 set
ind_d_3102_029 = (select countsite
from some_table
where province = (select province
from v_sitenumber_d_province_range
where sitenum = par_sitenum
)
);
end;
It might, or might not work - there's a possibility of TOO_MANY_ROWS if select returns more than a single value. I don't know, as I don't have your tables, so - that might need to be fixed.
If you insist on the loop, then consider such a code:
create or replace procedure p_sitenumber_range_d (par_sitenum in number)
is
begin
for cur_r in (select province
from v_sitenumber_d_province_range
where sitenum = par_sitenum
)
loop
update pm4h_db.ind_d_3102 set
ind_d_3102_029 = (select countsite
from some_table
where province = cur_r.province
);
end loop;
end;

Are you aware that you've built in a recursion?
The first thing you do during the procedure is to call up the procedure itself!

Related

ORACLE - Selecting Parameters for calling Procedure from Table

Is it possible to select the parameters for calling a procedure from the select statement?
EXECUTE PROCEDURE_NAME(para1,para2,para3,para4);
commit;
Is it possible to select para1,para2,para3,para4 from a select query?
EXECUTE PROCEDURE_NAME((SELECT PARA1,PARA2,PARA3,PARA4 FROM TABLEA))
COMMIT;
I do not have access to modify the procedure.
As a slight variation on what #vc74 suggested, you could just replace your EXECUTE command (which, assuming this is SQL*Plus or SQL Developer anyway, is just a wrapper for an anonymous block anyway) with an explicit anonymous block:
begin
for r in (SELECT PARA1,PARA2,PARA3,PARA4 FROM TABLEA) loop
PROCEDURE_NAME(r.PARA1,r.PARA2,r.PARA3,r.PARA4);
end loop;
end;
/
(I've left the bits from your original call uppercase and the new bits lower case mostly to distinguish them.)
Using a loop just means you don't need to declare local variables and select into those. It would also allow you to process multiple rows from the table, though I see form a comment you only expect one row. However, the flip side of that is it won't complain if there are no rows, or if there is more than one row, as the variable approach would do.
You could also use a record type to avoid declaring all the parameters separately:
declare
l_row tablea%rowtype;
begin
SELECT * into l_row FROM TABLEA;
PROCEDURE_NAME(l_row.PARA1,l_row.PARA2,l_row.PARA3,l_row.PARA4);
end;
/
This now does expect exactly one row to be found in the table.
You can call the functions in sql. So if you are able to create a function in your schema then you can do the following:
create a function function_name in your schema that calls the procedure procedure_name and returns some dummy result
use this function in sql query: select function_name(para1,para2,para3,para4) from tablea
example of function:
create or replace function function_name(
p1 varchar2,
p2 varchra2,
p3 varchar2,
p4 varchar2
) return number
is
begin
procedure_name(p1,p2,p3,p4); -- here you execute the procedure
return null;
end;

Sync between two tables and show the result in an oracle function

I am making an oracle function that sync values between two tables and My intention is to show a string that show how many rows affected and displays it when user execute the function.
my function creation is like this
CREATE OR REPLACE FUNCTION WELTESADMIN.DUM_MST_SYNC(PROJNAME IN VARCHAR2)
RETURN NUMBER IS NUM_ROWS NUMBER;
BEGIN
MERGE INTO MASTER_DRAWING DST
USING (SELECT WEIGHT_DUM, HEAD_MARK_DUM FROM DUMMY_MASTER_DRAWING) SRC
ON (DST.HEAD_MARK = SRC.HEAD_MARK_DUM)
WHEN MATCHED THEN UPDATE SET DST.WEIGHT = SRC.WEIGHT_DUM
WHERE DST.PROJECT_NAME = SRC.PROJECT_NAME_DUM
AND DST.PROJECT_NAME = PROJNAME;
dbms_output.put_line( sql%rowcount || ' rows merged' );
END;
if i execute the begin part in the TOAD or SQL Developer i can see how many rows affected. My target is to collect this function into a procedure and when user wants to sync tables they just need to run the procedure with PROJNAME value supplied for specific project.
Please help me on how to improve this code,
Best regards
You can use SQL%ROWCOUNT to get the number of rows affected by the MERGE. Add the following statement in your code immediately after the MERGE :
dbms_output.put_line( sql%rowcount || ' rows merged' );
To return this value, declare a NUMBER variable and assign the sql%rowcount value to it. And then return that value. Something like :
Function
.......
Return NUMBER
.......
num_rows NUMBER;
......
Begin
Merge....
num_rows := SQL%ROWCOUNT;
RETURN num_rows;
END;
And, you don't need a procedure to execute the function. You can execute in SQL :
select function(project_name)
from dual
/
Update Since OP is trying to use DML inside a function, need to make it autonomous transaction to be able to perform DML without raising the ORA-14551.
You could use the directive pragma autonomous_transaction. This will run the function into an independent transaction that will be able to perform DML without raising the ORA-14551. However, remember, the results of the DML will be committed outside of the scope of the parent transaction. If you have only single transaction, you could use the workaround that I suggested. Add, PRAGMA AUTONOMOUS_TRANSACTION; immediately after the RETURN statement before BEGIN.
CREATE OR REPLACE
FUNCTION WELTESADMIN.DUM_MST_SYNC(
PROJNAME IN VARCHAR2)
RETURN NUMBER
IS
num_rows NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
MERGE INTO MASTER_DRAWING DST USING
(SELECT WEIGHT_DUM, HEAD_MARK_DUM FROM DUMMY_MASTER_DRAWING
) SRC ON (DST.HEAD_MARK = SRC.HEAD_MARK_DUM)
WHEN MATCHED THEN
UPDATE
SET DST.WEIGHT = SRC.WEIGHT_DUM
WHERE DST.PROJECT_NAME = SRC.PROJECT_NAME_DUM
AND DST.PROJECT_NAME = PROJNAME;
num_rows := SQL%ROWCOUNT;
COMMIT;
RETURN num_rows;
END;
/

Creating a package to keep track of tapes used

Thought I had followed creation pattern, but the body will not compile. What I am trying to accomplish is to develop a package to run a procedrure periodically to determine at what time and date more than 15 are in use.. Oracle 11g.
The only other data that needs to go into the table beingg inserted into the sysdate.
CREATE OR REPLACE
PACKAGE TAPES_USED AS
function TAPESCOUNT(count number) return number;
procedure INSERT_TAPES_COUNT(sysdate date, count NUMBER);
END TAPES_USED;
/
-----------------------------------------
CREATE OR REPLACE
PACKAGE body TAPES_USED AS
function TAPESCOUNT(count number) return number as count number;
begin
select count(*)
into
count
from DEV.TAPES_IN USE where count(*) > 15;
procedure INSERT_TAPES_COUNT(sysdate date, count NUMBER)as
begin
INSERT INTO DEV.TAPES_USED VALUES
(sysdate, count);
end INSERT_TAPES_COUNT;
END TAPES_USED;
/
Any help or suggestion anyone can offer will be appreciated.
CREATE OR REPLACE
PACKAGE BODY tapes_used AS
FUNCTION tapescount(in_ct NUMBER) RETURN NUMBER IS
ct NUMBER;
BEGIN
SELECT COUNT(*)
INTO ct
FROM dev.tapes_in_use;
IF ct > in_ct THEN
RETURN ct;
ELSE
RETURN NULL;
END IF;
END tapescount;
PROCEDURE insert_tapes_count(sysdt date, ct NUMBER) IS
BEGIN
INSERT INTO dev.tapes_used VALUES (sysdt, ct);
END insert_tapes_count;
END tapes_used;
/
You should refrain from using reserved words such as COUNT and SYSDATE for variable names (I don't know but that could be some of your compilation issues), so I've renamed them. Also, you forgot to END your function. I think you were missing an underscore in your table name in the FROM clause of the SELECT in your function, and you didn't have a RETURN statement in your function, which you must have.
Generally speaking, a function should accept one or more input parameters and return a single value. You're not making use of the input parameter in your function. I've implemented a suggested parameter.
As Egor notes, this isn't a realistic function, and I'm not certain about your intent here. What is the function supposed to do?
Maybe you want your function to return the Date/Time your count was exceeded? You could also combine everything into a single procedure:
PROCEDURE ck_tape_ct(min_tape_ct NUMBER) IS
ct NUMBER;
BEGIN
SELECT COUNT(*)
INTO ct
FROM dev.tapes_in_use;
IF ct > min_tape_ct THEN
INSERT INTO dev.tapes_used VALUES(SYSDATE, ct);
END IF;
END;

Nested if statement is returning a statement handle not executed error

I have a stored procedure with two input, two output params and 5 sys_refcursors.
I had a succesful IF/ELSE where I opened these cursors by calling different stored procs but now need a third option which is another stored proc call. The third option is virtually identical to the second with one difference.
I was pretty sure I got the nested if statement correct but I keep getting ora-24338 Statement handle not executed when it tries to get the cursors from the this new call.
The problem stored procedure call is the middle one.
create or replace Procedure procedure_name (
OutVar out varachar2,
Outvar2 out number,
inParam1 date,
REf-Cur1 in out sys_refcursor,
REf-Cur2 in out sys_refcursor,
REf-Cur3 in out sys_refcursor,
REf-Cur4 in out sys_refcursor,
REf-Cur5 in out sys_refcursor
)
is
tIsBindVar1 varchar2(100);
tIsBindVar2 varchar2(100);
tOutVar1 varchar2(100);
TOutVar2 varchar2(100);
Begin
Select Max(T.Var1)
into tIsBindVar1
From table1
where T.aField = inParam1;
Select Function_Name (inParam1)
into tIsBindVar2
from Dual;
IF tIsBindVar1 is NOT NULL
THEN
Select P.Field_A P.Field_B
INTO tOutVar1, tOutVar2
FROM table1
WHERE P.Field_A = inParam1;
Stored_Proc_One (tInParam => tOutVar1,
inParam1 => inParam1,
5 cursors => 5 cursors);
ELSE
IF tIsBindVar2 = 'Y'
THEN
Stored_Proc_Two (inParam1 => inParam1,
5 cursors => 5 cursors);
ELSE
Stored_Proc_Three ();
Stored_Proc_Two ( inParam1 => inParam1, 5 cursors => 5 cursors);
END IF;
END IF;
SELECT tOutVar1, tOutVar2
INTO OutVar1, OutVar2
FROM DUAL;
Some quick extra notes.
Stored_procs one and two are straight up data grabs, nothing fancy, stored proc 3 generates some data based on some input params (not listed) and stored proc 2 is called to collect.
I can change these stored proc calls around and I always wind up with the same error from the middle one. That includes changing the conditions in any order.
I tried to simplify the code as I'm not looking for anyone to do the work for me but to try and get understanding what the problem is.
Hopefully I did not leave anything important out but I think the issue is something with how I'm doing the nested if. I certainly don;t think the issue is in the the stored procs themselves as I say they work when I change the order.
So if long winded and very hard to read code. Going to try and find where they keep the editing info and clean it up.
Thanks in advance.
What are the values of tIsBindVar1 and tIsBindVar2?
Do you really want the IF statement structured the way I formatted it? Or do you really want
IF tIsBindVar1 is NOT NULL
THEN
<<do something>>
ELSIF tIsBindVar2 = 'Y'
THEN
<<do something else>>
ELSE
<<do one more thing>>
END IF;
If you want to ensure that at least one path is followed in all cases, you'd want an IF ELSIF ELSE.
Incidentally, there is no need for all those SELECT FROM dual statements. You can simply assign variables in PL/SQL
tIsBindVar2 := Function_Name (inParam1);
and
tOutVar1 := OutVar1;
tOutVar2 := OutVar2;
are more conventional.

Cursor design and refactoring question

I have many cursors that all return rows with the same fields: a numeric ID field and an XMLType field. Every time I access one of these cursors (each cursor has now got its own function for access), I go through the same pattern:
--query behind cursor is designed to no more than one row.
for rec in c_someCursor(in_searchKey => local_search_key_value) loop
v_id := rec.ID
v_someXMLVar := rec.XMLDataField
end loop;
if v_someXMLVar is null then
/* A bunch of mostly-standard error handling and logging goes here */
end if;
exception
/* all cursor access functions have the same error-handling */
end;
As the pattern became more obvious, it made sense to centralize it in a single function:
function fn_standardCursorAccess(in_cursor in t_xmlCursorType, in_alt in XMLType) return XMLType is
v_XMLData XMLType;
begin
dbms_application_info.set_module(module_name => $$PLSQL_UNIT, action_name => 'fn_standardCursorAccess');
loop
fetch in_cursor
into v_XMLData;
exit when in_cursor%notfound;
end loop;
/*some additional standard processing goes here*/
return v_XML;
exception
/*standard exception handling happens here*/
end;
The problem I've run into is in calling this function. I now have to call it like this:
open v_curs for select /*blah blah blah*/ where key_field = x and /*...*/;
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;
What I'd like to do is call it like this:
open v_curs for c_getSomeData(x);
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;
...reason being to minimize the amount of changes to my code (I don't want to have to cut/paste all these cursors to the functions that depend on them, and in the case where multiple functions depend on the same cursor, I'll have to wrap that in a new function).
Unfortunately, this does not work, Oracle returns an error saying
Error: PLS-00222: no function with name 'C_GETSOMEDATA' exists in this scope
Is what I'm trying to do even possible?
(Oracle version is 10.2)
EDIT:
I think a better way to describe what I'm doing is pass a reference to an explicit cursor to a function that will perform some common routines on the data returned by the cursor.
It appears that I cannot use an open-for statement with an explcit cursor, is there any other way to get a reference to an explicit cursor so I can pass that reference to a function? Maybe there is some other way I could approach this problem?
EDIT:
Copying and pasting from my earlier reply to R Van Rijn's reply:
I tried declaring the cursor in the package specification, and referencing it with the package name: open v_curs for PKG.c_getSomeData(x);... This gives me a new error, saying that PKG.c_getSomeData must be a function or array to be used that way.
UPDATE:
I spoke to our DBA here, he says it is not possible to have a ref cursor point to an explicit cursor. It looks like I can't do this after all. Bummer. :(
concerning the Error PLS-00222:
An identifier being referenced as a function 'c_getSomeData' was not declared or actually represents another object (for example, it might have been declared as a procedure).
Check the spelling and declaration of the identifier. Also confirm that the declaration is placed correctly in the block structure
This means you must create a function that actually returns some value(s).
Does this test script and output represent what you are trying to do? Instead of open v_curs for c_getSomeData(x); I'm setting the cursor variable = to the output from the function.
Our Test Data:
set serveroutput on
--create demo table
drop table company;
create table company
(
id number not null,
name varchar2(40)
);
insert into company (id, name) values (1, 'Test 1 Company');
insert into company (id, name) values (2, 'Test 2 Company');
insert into company (id, name) values (3, 'Test 3 Company');
commit;
Create Packages
create or replace package test_pkg as
type cursor_type is ref cursor;
function c_getSomeData(v_companyID number) return cursor_type;
end test_pkg;
/
create or replace package body test_pkg as
function c_getSomeData(v_companyID number) return cursor_type
is
v_cursor cursor_type;
begin
open v_cursor for
select id,
name
from company
where id = v_companyID;
return v_cursor;
end c_getSomeData;
end test_pkg;
/
Run Our Procedure
declare
c test_pkg.cursor_type;
v_id company.id%type;
v_name company.name%type;
begin
c := test_pkg.c_getSomeData(1);
loop
fetch c
into v_id, v_name;
exit when c%notfound;
dbms_output.put_line(v_id || ' | ' || v_name);
end loop;
close c;
end;
/
1 | Test 1 Company
PL/SQL procedure successfully completed.
I confess to finding your requirements a trifle hard to divine. You have posted a lot of code, but as I suggested in my comment, not the parts which would illuminate the problem. So possibly the following is way off-beam. But it is an interesting issue.
The following code shows how we can define a common, geneneric REF CURSOR, populate it with specific data from different queries, and then process them in a standardised fashion. Again, I apologise if this does not fit your business logic; if such is the case, please edit your question to explain where I have made a bloomer..
Here is the generic ref cursor. ...
create or replace package type_def is
type xml_rec is record (id number, payload xmltype);
type xml_cur is ref cursor return xml_rec;
end type_def;
/
and here is the standatd processor
create or replace procedure print_xml_cur
( p_cur in type_def.xml_cur )
is
lrec type_def.xml_rec;
begin
loop
fetch p_cur into lrec;
exit when p_cur%notfound;
dbms_output.put_line('ID='||lrec.id);
dbms_output.put_line('xml='||lrec.payload.getClobVal());
end loop;
close p_cur;
end print_xml_cur;
/
Two procedures which return the standard cursor with different data....
create or replace function get_emp_xml
( p_id in emp.deptno%type )
return type_def.xml_cur
is
return_value type_def.xml_cur;
begin
open return_value for
select deptno
, sys_xmlagg(sys_xmlgen(ename))
from emp
where deptno = p_id
group by deptno;
return return_value;
end get_emp_xml;
/
create or replace function get_dept_xml
( p_id in dept.deptno%type )
return type_def.xml_cur
is
return_value type_def.xml_cur;
begin
open return_value for
select deptno
, sys_xmlagg(sys_xmlgen(dname))
from dept
where deptno = p_id
group by deptno;
return return_value;
end get_dept_xml;
/
Now let's put it all together ....
SQL> set serveroutput on size unlimited
SQL>
SQL> exec print_xml_cur(get_emp_xml(40))
ID=40
xml=<?xml
version="1.0"?>
<ROWSET>
<ENAME>GADGET</ENAME>
<ENAME>KISHORE</ENAME>
</ROWSET>
PL/SQL procedure successfully completed.
SQL> exec print_xml_cur(get_dept_xml(20))
ID=20
xml=<?xml version="1.0"?>
<ROWSET>
<DNAME>RESEARCH</DNAME>
</ROWSET>
PL/SQL procedure successfully completed.
SQL>
OK, so the short answer from Oracle is: "can't be done!"
The short answer from me is: "Yeah - like Oracle is gonna stop me! So yes you can....but you need to be sneaky ... oh yes, and there is a 'but' or two....in fact...ugh!"
So, how can you pass your explicit cursor by reference? By nesting it into another cursor using the CURSOR() construct!
e.g.)
CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor;
end;
/
CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor
is
test_Cur sys_refcursor;
cursor gettest is
select CURSOR( -pass our actual query back as a nested CURSOR type
select ELCTRL_EVNT_ELCTRL_DISTRCT_ID,
ELECTORAL_DISTRICT_ID,
ELECTORAL_EVENT_ID
from ELCTRL_EVNT_ELCTRL_DISTRCT
where electoral_District_id = ed_id)
from dual;
begin
open gettest;
fetch gettest into test_Cur;
return test_Cur;
end;
end;
/
So what is the problem with this solution? It has a leak! The outer gettest cursor is never closed, because we don't close it and the client will only close the reference to the nested cursor that was selected for them - not the main cursor. And we can't close it automatically because closign the parent would force closing the nested cursor that you have returned by reference - and it is entirely likely that the client has not done using it.
So we have to leave a cursor open in order to return the nested cursor.
And if the user tried calling get_Cursor again with a new value of ed_id, they would discover that the session persistence in the package meant that the cursor handle is still in use and an error would be raised.
Now, we could fix that by first checking and closing the explicit cursor:
if gettest%isopen then
close gettest;
end if;
open gettest;
fetch gettest into test_Cur;
return test_Cur;
But still - what if the user never calls this again? How long 'til Oracle garbage-collects the cursor? And how many users running how many sessions calling how many functions that use this construct will be leaving cursors open after they are done with them? Better count on a huuuuuge overhead to leave all those open cursors layin' about!
No, you would need to have the users do a callback to explicitely close it or you would be clogging the database. But doing this would require changing the scope of the explicit cursor so that both functions can access it: So we need to make it at package scope, not function scope
CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor;
function close_cursor return sys_refcursor;
end;
/
CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
cursor l_gettest(p_ed_id in number) is
select CURSOR(
select ELCTRL_EVNT_ELCTRL_DISTRCT_ID, ELECTORAL_DISTRICT_ID, ELECTORAL_EVENT_ID
from ELCTRL_EVNT_ELCTRL_DISTRCT
where electoral_District_id = p_ed_id)
from dual;
function get_cursor(ed_id number) return sys_refcursor
is
l_get_Cursor sys_refcursor;
begin
open l_gettest (ed_id);
fetch l_gettest into l_get_Cursor;
return l_get_cursor;
end;
function close_cursor return sys_refcursor
is
begin
if l_gettest%isopen then
close l_gettest;
end if;
return pkg_common.generic_success_cursor;
end;
end;
/
OK, plugged the leak. Except it cost us a network round trip instead of the hard parse,...oh wait - and also except embedding a bind variable into an explicit cursor declared at this level is probably going to cause scoping issues of its own which was the reason we wanted to do this in the first place!
Oh, and in a session-pooling environment can two users step on each other's cursors? IF they aren't very carefull about doing an open-fetch-close before returning the session to the pool - we could wind up with some really interesting (and impossible to debug) results!
And how much do you trust the maintainers of the client code to be extra-diligent on this? YEah - me too.
So the short answer is: Yes, with a bit of sneakiness it could be done despite Oracle saying it can't.
The better answer is: But please don't! The extra round trip and potential for memory leaks and client code errors causing data problems makes this a very scary proposition.
It appears that what I wanted to do (have an open-for statement reference an existing explicit cursor) is simply not allowed in Oracle. :(

Resources