ORA-04021: timeout occurred while waiting to lock object - oracle

I have this anonymous PL/SQL block which calculates and prints a value return from a table.
DECLARE
U_ID NUMBER :=39;
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID INTO RETAIL, FLAG FROM UNITS WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID) INTO FLAG FROM UNITS WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN EXIT; END IF;
SELECT RETAIL* RETAIL_AMOUNT INTO RETAIL FROM UNITS WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
DBMS_OUTPUT.PUT_LINE( RETAIL);
END;
This block work correctly, but I wanted to do the same thing using a PL/SQL Function
I wrote the function as follow:
CREATE OR REPLACE FUNCTION GET_UNIT_RETAIL(U_ID NUMBER)
RETURN NUMBER
IS
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID
INTO RETAIL, FLAG
FROM UNITS
WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID)
INTO FLAG
FROM UNITS
WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN
EXIT;
END IF;
SELECT RETAIL* RETAIL_AMOUNT
INTO RETAIL
FROM UNITS
WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
RETURN NUMBER;
END;
/
When I try to execute the above code to save the function to the database, the environment (SQL*PLUS) hangs for a long time and at the end returns this error:
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object
What is the problem ??? Please !

Sounds like ddl_lock problem
Take a look at
dba_ddl_locks to see who is "blocking" a create or replace.
Also try to create under different name - and see what happens.

The problem was because the Object GET_UNIT_RETAIL was busy by other environment
Here is the answer:
https://community.oracle.com/thread/2321256

Related

how do I use if condition in cursor because our professor don't allow us use where clause in the select statement

Our question is showing all the countries that have names that are exactly 5 letter long. This is the cursor code and I want add if condition into it.
declare
cursor cursor_full is select * from country_cont;
begin
for counter in cursor_full
loop
dbms_output.put_line(counter.country|| ' ' || counter.continent);
end loop;
end;
However my professor said that you can't using where clause within the select statement and you should display all the countries and continent.
so i tried this code:
declare
country varchar(50);
cursor cursor_full is select * from country_cont;
begin
if length(country)=5 then
for counter in cursor_full
loop
dbms_output.put_line(counter.country|| ' ' || counter.continent);
end loop;
end if;
end;
the script output show that PL/SQL procedure successfully completed but nothing return in DBMS output
Hope someone can help me, I spent whole night to think about it,please!
Variable country doesn't contain any value, it is null so if condition is never true and loop is never executed. Sample data would help; meanwhile, see if this helps.
begin
for cur_r in (select * from country_cont) loop
if length(cur_r.country) > 5 then
dbms_output.put_line(cur_r.country|| ' ' || cur_r.continent);
end loop;
end;
Don't forget to set serveroutput on.

Oracle Function that return table of number problems and performance

I have (sometimes) a memory block in my oracle database that turn crasy... a lot of session sundenless block each other and the probleme is in a function that return a table of number and is use in another procedure.
Edit : Sessions is 'blocked' with Read By Other Session Wait Event
First, my table of number :
CREATE OR REPLACE TYPE liste_lots as TABLE OF number(10)
and in large the function who populate the table :
function get_ot_idem_cursor( .. ) return liste_lots is
res_type liste_lots;
p_restriction_level number;
cursor curs_lvl_1 is select [...] ;
row_lvl_1 curs_lvl_1%rowtype;
cursor curs_lvl_0 is select [...] ;
row_lvl_0 curs_lvl_0%rowtype;
begin
res_type := liste_lots();
p_restriction_level := get_edi_line_restriction(p_edi_line);
if p_restriction_level = 1 then
open curs_lvl_1;
loop
fetch curs_lvl_1 into row_lvl_1;
exit when curs_lvl_1%notfound;
begin
res_type.extend;
res_type(res_type.last) := row_lvl_1.lot_id;
exception
when others then
dbms_output.put_line('problème get_ot_idem_cursor ');
dbms_output.put_line(sqlerrm);
close curs_lvl_1;
end;
end loop;
close curs_lvl_1;
else
open curs_lvl_0;
loop
fetch curs_lvl_0 into row_lvl_0;
exit when curs_lvl_0%notfound;
begin
res_type.extend;
res_type(res_type.last) := row_lvl_0.lot_id;
exception
when others then
dbms_output.put_line('problème get_ot_idem_cursor ');
dbms_output.put_line(sqlerrm);
close curs_lvl_0;
end;
end loop;
close curs_lvl_0;
end if;
return res_type;
exception
when others then
if curs_lvl_0%isopen then
close curs_lvl_0;
end if;
if curs_lvl_1%isopen then
close curs_lvl_1;
end if;
end;
and is used on another part like that :
liste_ots := get_ot_idem_cursor(v_lot, v_sr_ligne_lot.id );
select min(l.lot_id) into result
from lot l
where l.des_tiers_id = p_pf_tiers_id
and l.lot_nature = 'POS'
and l.exp_tiers_id = v_sr_ligne_lot.ramasse_tiers_id
and ot_id in ((select * from TABLE(liste_ots)))
and l.lot_datheurcharg > sysdate - 3;
When the db become crasy (session block, very slow) this is the part of the code who is pointed :
select * from TABLE(liste_ots)
the problem is not all the time, then if you have any idea or advise...
thanks in advance (sorry for my bad english)
Use a bulk collect instead of a plain loop (fetching records one by one) as there is no additional logic done in the loop. In general, always avoid switching a context (SQL to/from PL/SQL)
open curs_lvl_1;
fetch curs_lvl_1 bulk collect into res_type;
close curs_lvl_1;

In Oracle, how to catch exception when table used for the loop doesn't exists

I have this code:
BEGIN
FOR
U1 IN (SELECT * FROM SOME_USER.SOME_TABLE)
LOOP
BEGIN
-- do something;
END;
END LOOP;
END;
My problem is that sometimes SOME_USER.SOMETABLE do not exists but I want the rest of the script to be run. I know that checking if the table exists before running the code (in a IF ... THEN block) will not work because SELECT * FROM SOME_USER.SOME_TABLE is evaluated at compile time.
So another avenue is to run the SELECT with EXECUTE IMMEDIATE. This way it will be evaluated at run time and I would be able to catch the exception. Unfortunately I can't find a way to use EXECUTE IMMEDIATE with my U1 IN loop. How I should achieve this?
I'm on Oracle 11g and the SQL script is run from a batch script on Windows.
You can use the 'OPEN FOR' syntax:
DECLARE
CUR SYS_REFCURSOR;
<variables or record type> -- declare as appropriate
BEGIN
OPEN CUR FOR 'SELECT * FROM SOME_USER.SOME_TABLE';
LOOP
FETCH CUR INTO <variables or record type>;
EXIT WHEN CUR%NOTFOUND;
-- do something with variables or record
END LOOP;
CLOSE CUR;
END;
/
You need to fetch each row into variables or a record type, you can't use %ROWTYPE as the table still won't exist; and you can change to do bulk fetches if that's appropriate for your data volumes.
If you run that you'll still get ORA-00942, but if this is in a stored program you won't get it until run time, and you can now add an IF block to check for the table's existence before the OPEN.
Having a data model where objects may or may not exist at run-time seems rather fishy though...
Proposed solution with cursor is fine, I would add an exception handling for this particular exception : Table or view does not exist ORA-00942.
DECLARE
e_missing_t EXCEPTION;
pragma exception_init (e_missing_t,-942);
something number; --some variable you need to fetch to
CUR SYS_REFCURSOR;
BEGIN
OPEN CUR FOR 'SELECT * FROM SOME_USER.SOME_TABLE';
LOOP
FETCH CUR INTO something;
EXIT WHEN CUR%NOTFOUND;
-- do something with variables or record
END LOOP;
CLOSE CUR;
EXCEPTION
WHEN e_missing_t THEN
dbms_output.put_line('some_table is missing');
END;
/
You could possibly use a workaround -
Create a nested table type and store the results of the SELECT in it. Use that type to loop through values.
So,
SELECT data_obj(COL1, COL2) bulk collect into data_tbl_typ from data_table;
This part can go in the dynamic sql. (Remember to use bind variables)
And then just loop through this nested table type in your procedure.
Use the DBMS_SQL package to run the query.
Follow the examples in this Oracle documentation:
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm#sthref6147
pl/sql has the exception clause for that. OTHERS catches pretty much everything. You can deal with the exception in the function, or print a message and pass it back to the main. Break your function up into smaller functions and have each one catch it's own exception.
BEGIN
FOR
U1 IN (SELECT * FROM SOME_USER.SOME_TABLE)
LOOP
BEGIN
-- do something;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Oh well. The table isn't there.');
--RAISE;
END;

sql command not ended properly after executing the function

for example I have created a table with table name 'aaa' with four columns act_num, clear_balance, available_balance, total_balance and I have inserted some values.
The function
deb_amount withdraws money from a bank account. It accepts an account
number and an amount of money as parameters. It uses the account number to
retrieve the account balance from the database, then computes the new balance. If this
new balance is less than zero then the function jumps to an error routine; otherwise,
it updates the bank account.
create or replace function deb_amount(p_act_num VARCHAR2, p_amount number )
return number as
declare
v_old_amount number;
v_new_amount number;
e_over_drawn exception;
begin
select clear_balance into v_old_amount from aaa where act_num=p_act_num;
v_new_amount:=v_old_amount-p_amount;
if v_old_amount<p_amount then
raise e_over_drawn;
else
update aaa set clear_balance=v_new_amount,available_balance=v_new_amount,total_balance=v_new_amount where act_num=p_act_num;
end if;
commit;
return clear_balance;
exception
when e_over_drawn then
rollback;
end;
it will compile, but with warnings.
If I want to execute the 'select * from deb_amount(1,100)' it show error.
sql command not ended properly.
Thank you.
You need to call function using dual. Ex:
select deb_amount(1,100) from dual;
or using a variable in plsql block
declare
l_return number;
begin
l_return:=deb_amount(1,100);
end;
It looks like you might be running several commands as a scipt, but haven't ended the function properly. The / after the function creation has to be on a line on its own, and at the start of the line:
create or replace function deb_amount(p_act_num VARCHAR2,
p_amount number)
return number as
declare
v_old_amount number;
v_new_amount number;
e_over_drawn exception;
begin
select clear_balance into v_old_amount
from aaa where act_num=p_act_num;
v_new_amount:=v_old_amount-p_amount;
if v_old_amount<p_amount then
raise e_over_drawn;
else
update aaa set clear_balance=v_new_amount,
available_balance=v_new_amount,
total_balance=v_new_amount
where act_num=p_act_num;
end if;
commit;
return clear_balance;
exception
when e_over_drawn then
rollback;
end;
/
show errors
select deb_account('1', 1) from dual;
The show errors will tell what actual compilation errors you got. It looks like it will complain about the return as you don't have a local clear_balance variable, but you can use v_new_amount instead here. You need to return something after the rollback too, or raise an exception which might be more useful.
As Manjunatha said, your query then needs to call the function properly, with the from clause referencing a table, rather than the function itself.
You have a bigger problem with the concept though; you can't call a function that does DML (insert, update, delete) from SQL, only from a PL/SQL block. Generally DML should be done from a procedure rather than a function, if it has to be done in PL/SQL at all.

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