DELETE FROM inside a PL/SQL Procedure Block - oracle

I am currently trying to delete all rows of a table that is linked to another one via foreign key.
My Code looks kinda like this:
CREATE OR REPLACE Procedure test
BEGIN
DELETE FROM person;
End;
/
The Errors says: ora-02292 integrity constraint violated - child record found
When I try to just diable the constraints then it says i cant 'ALTER' the table.
What do i have to do/change?

You shouldn't just disable constraint, because you'll leave child records orphans (there will be NO parent record for them). What will you do, then?
Correct way to handle it is to
delete children first
delete parents last
If foreign key constraint was created with the on delete cascade option, database would handle it for you.
P.S. As of "you can't ALTER the table" - that's not an Oracle error message. They have their codes, such as ORA-06550. It is difficult to guess what you actually did, and - if I had to guess - I'd say that you tried to do that within the procedure:
SQL> create table temp (id number constraint pkt primary key);
Table created.
SQL> begin
2 alter table temp disable constraint pkt;
3 end;
4 /
alter table temp disable constraint pkt;
*
ERROR at line 2:
ORA-06550: line 2, column 3:
PLS-00103: Encountered the symbol "ALTER" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
You'll need dynamic SQL to do that:
SQL> begin
2 execute immediate 'alter table temp disable constraint pkt';
3 end;
4 /
PL/SQL procedure successfully completed.
SQL>
But, once again, that's not the way you should handle this situation.

Related

ORA-04091 Mutating Table error during AFTER INSERT Trigger

I have the following AFTER INSERT trigger on a table called DM_USER_ROLE
create or replace TRIGGER "DM_USER_ROLE_T1"
AFTER
insert on "DM_USER_ROLE"
for each row
DECLARE
v_cert_enrolment_id number;
v_user_role_id number;
begin
v_cert_enrolment_id := "DM_CERTIFICATION_ENROLMEN_SEQ".nextval;
v_user_role_id := :new.USER_ROLE_ID;
/*
When a user is assigned a role, we create an enrolment record
in DM Certification record linked to this user/role combination.
We also insert into the DM_COURSE_ENROLMENT table the courses
associated with the certfication
*/
--FIRST AN ENROLMENT RECORD IS CREATED IN DM_CERTIFICATION_ENROLMENT
INSERT INTO DM_CERTIFICATION_ENROLMENT
(CERTIFICATION_ENROLMENT_ID, ALLOCATED_DT, DEADLINE_DATE, STATUS, USER_ROLE_ID)
VALUES
(
v_cert_enrolment_id,
trunc(sysdate),
trunc(sysdate) + 60,
'Enrolled',
v_user_role_id
);
--COURSES LINKED TO THE CERTIFICATION ARE INSERTED INTO DM_COURSE_ENROLMENT
INSERT INTO DM_COURSE_ENROLMENT
(
CERTIFICATION_ENROLMENT_ID,
COURSE_ID,
ALLOCATED_DT,
DEADLINE_DT,
STATUS
)
SELECT v_cert_enrolment_id,
COURSE.COURSE_ID,
trunc(sysdate),
trunc(sysdate) + 60,
'Enrolled'
FROM DM_CERTIFICATION_COURSE COURSE
WHERE CERTIFICATION_ID =
(
SELECT C.CERTIFICATION_ID FROM
DM_CERTIFICATION_ENROLMENT A,
DM_USER_ROLE B,
DM_ROLE_CERTIFICATION C
WHERE
A.USER_ROLE_ID = B.USER_ROLE_ID
AND
B.ROLE_ID = C.ROLE_ID
AND
A.CERTIFICATION_ENROLMENT_ID = v_cert_enrolment_id
);
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20299)));
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM, 1, 2000));
end;
I need to populate 2 separate tables when an insert happens in this table, and I thought AFTER INSERT triggers avoided issues with mutating tables?
I am not sure what is causing it, perhaps the read in the second INSERT statement from DM_USER_ROLE, which is where this trigger is initiated...but I was under the impression AFTER INSERTs were safe to avoid mutations, as the update has already happened.
Error is:
ORA-04091: table AZLEARN_BACKUP.DM_USER_ROLE is mutating,
trigger/function may not see it
The first insert happens, the second one does not.
This article led me to believe AFTER triggers were safe.
http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm
-------UPDATE---------------
I changed it to do row by row insert using two parameterised cursors and it worked...still not sure what the error was:
create or replace TRIGGER "DM_USER_ROLE_T1"
AFTER
insert on "DM_USER_ROLE"
for each row
DECLARE
v_cert_enrolment_id number;
v_user_role_id number;
v_role_id number;
v_certification_id number;
cursor certs_for_role(p_role_id number) is
select * from DM_ROLE_CERTIFICATION where ROLE_ID = p_role_id;
r_certs_for_role certs_for_role%rowtype;
cursor courses_for_certs(p_cert_id number) is
select * from DM_CERTIFICATION_COURSE where CERTIFICATION_ID = p_cert_id;
r_courses_for_certs courses_for_certs%rowtype;
begin
v_user_role_id := :new.USER_ROLE_ID;
v_role_id := :new.ROLE_ID;
open certs_for_role(v_role_id);
loop
fetch certs_for_role into r_certs_for_role;
exit when certs_for_role%notfound;
v_cert_enrolment_id := "DM_CERTIFICATION_ENROLMEN_SEQ".nextval;
INSERT INTO DM_CERTIFICATION_ENROLMENT
(CERTIFICATION_ENROLMENT_ID, ALLOCATED_DT, DEADLINE_DATE, STATUS, USER_ROLE_ID, CERTIFICATION_ID)
VALUES
(
v_cert_enrolment_id,
trunc(sysdate),
trunc(sysdate) + 60,
'Enrolled',
v_user_role_id,
r_certs_for_role.CERTIFICATION_ID
);
open courses_for_certs(r_certs_for_role.CERTIFICATION_ID);
loop
fetch courses_for_certs into r_courses_for_certs;
exit when courses_for_certs%notfound;
INSERT INTO DM_COURSE_ENROLMENT
(
CERTIFICATION_ENROLMENT_ID,
COURSE_ID,
ALLOCATED_DT,
DEADLINE_DT,
STATUS
)
VALUES
(
v_cert_enrolment_id,
r_courses_for_certs.COURSE_ID,
trunc(sysdate),
trunc(sysdate) + 60,
'Enrolled'
);
end loop;
close courses_for_certs;
end loop;
close certs_for_role;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20299)));
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM, 1, 2000));
end;
The reason is simply you cannot select from table DM_USER_ROLE where the ROW LEVEL trigger is based on.
In your first solution you have a
SELECT ...
FROM DM_USER_ROLE ...
This is not allowed. Your second trigger does not select table DM_USER_ROLE thus it is working.
The advise in linked page is correct but misleading when they state 'Use an "after" or "instead of" trigger' - It should be more precisely 'Use an "after statement" or "instead of" trigger' . Oracle provides triggers based on following actions:
DML statements (INSERT, UPDATE, DELETE) on a particular table or view
DDL statements (CREATE or ALTER primarily)
Database events, such as logon/logoff, errors, or startup/shutdown
You have a DML trigger which can have different timing points:
Before the triggering statement executes
After the triggering statement executes
Before each row that the triggering statement affects
After each row that the triggering statement affects
Compound Trigger -> this one combines the four triggers listed above
INSTEAD OF Trigger (only for views)
Many people miss the difference between a statement level trigger and a row level trigger.
Row Level Triggers have keyword FOR EACH ROW and run for each row like the keyword implies. If you skip the FOR EACH ROW keyword, then the trigger is executed only once for each statement, no matter how many rows are affected by your INSERT/UPDATE/DELETE statement.
The most likely cause of a mutating table error is the misuse of triggers. Here is a typical example:
1.you insert a row in table A
2.a trigger on table A (for each row) executes a query on table A, for example to compute a summary column
3.Oracle throws an ORA-04091: table A is mutating, trigger/function may not see it
This is an expected and normal behaviour, Oracle wants to protect you from yourself since Oracle guarantees:
•(i) that each statement is atomic (i.e will either fail or succeed
completely)
•(ii) that each statement sees a consistent view of the data
Now in your trigger, when you do the second insert, it's doing a join on the DM_USER_ROLE table to fetch the records and that's the reason you face
ORA-04091: table AZLEARN_BACKUP.DM_USER_ROLE is mutating,
trigger/function may not see it

Oracle 11g: Compound triggers with an "on delete cascade" constraint

In Oracle (11.2.0.1) I have 2 tables, let's say A and B with a referencial constraint between them ("on delete cascade"). A is a "parent" for B.
create table A (id number(9) not null primary key);
create table B (id number(9) not null primary key, parent_id not null references A(id) on delete cascade);
The child table also has a related compound trigger on deleting from it (The main purpose is to avoid mutation errors in a series of complex integrity checks).
create or replace trigger trg_b
for delete on b
compound trigger
type b_table is table of b%rowtype;
b_collection b_table := b_table();
before each row is
begin
b_collection.extend;
b_collection(b_collection.last).id := :old.id;
b_collection(b_collection.last).parent_id := :old.parent_id;
end before each row;
after statement is
begin
for i in b_collection.first .. b_collection.last loop
--logging into another table in an autonomous transaction
end loop;
b_collection.delete;
end after statement;
end trg_b;
One of the algorithms to work with these tables is following:
All rows with the specific parent_id will be deleted from B.
Additional check if no child rows are left.
Delete this specific id from the parent A.
In our production enviroment Step 3 generates an exception ORA-06502: PL/SQL: numeric or value error at string b_collection.first .. b_collection.last. It means a deletion from the parent table leads to firing the trigger on the child table even though there are no possible child rows on affect. It could make some sense since the collection doesn't exist when trigger is fired and the first and last collection indexes are NULL, but I just can not reproduce such behaviour.
In our dev enviroment I only get an ORA-06502 when I try to delete some non-existent id/parent_id from B. In its turns when I try to delete some existent (with no child rows) or non-existent id from A I get no errors - 0 rows deleted, 0 child rows affected. From logging I could tell the child related trigger isn't even fired in such cases. Why not?
Any ideas why the described behaviour may be so different? Did I miss something?
I figured it out. The Bug 8830338 - BEFORE and AFTER STATEMENT not executed in compound trigger for DELETE CASCADE (Doc ID 8830338.8) for 11.2.0.1 was involved into my fun with triggers. It's claimed to be fixed since the patchset 11.2.0.2, ever since such a compound trigger with an after statement should've been fired and ended up in a bad way.
You will get the ORA-06502: PL/SQL: numeric or value error exception whenever your collection is empty:
SQL> declare
2 type t is table of number;
3 numtab t := t();
4 begin
5 for i in numtab.first..numtab.last loop
6 null;
7 end loop;
8 end;
9 /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at line 5
Unless there is any way the collection could end up being sparse (i.e. have some members deleted from the middle) I would use this loop instead:
1 declare
2 type t is table of number;
3 numtab t := t();
4 begin
5 for i in 1..numtab.count loop
6 null;
7 end loop;
8* end;
SQL> /
PL/SQL procedure successfully completed.
Otherwise you need to test whether the collection is empty or not before processing it (which always seems excessively cumbersome to me when collections are known to be dense):
1 declare
2 type t is table of number;
3 numtab t := t();
4 begin
5 if numtab.count > 0 then
6 for i in numtab.first..numtab.last loop
7 null;
8 end loop;
9 end if;
10* end;
SQL> /
PL/SQL procedure successfully completed.
instead of delete cascade , write your own trigger before delete on master table to delete the Childs records . Then the delete compound trigger will be fired .

Error(5,1): PLS-00103: Encountered the symbol "CREATE" error while creating function

Error(5,1): PLS-00103:
Encountered the symbol "CREATE" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma raise return select update while with <an identifier> <a double-quoted delimited-identifier> <a bind variable> << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge
Below the code I've written.
CREATE OR replace FUNCTION First_three_records
RETURN NUMBER AS
BEGIN
CREATE TEMPORARY TABLE temp_emp ON COMMIT DROP AS
SELECT *
FROM emp
WHERE deptno=20;
INSERT INTO tgt
SELECT *
FROM temp_emp;
END;
Oracle does not have local temporary tables, and you can't create objects within a PL/SQL block unless you use dynamic SQL; and it's very rarely necessary or a good idea. Your schema should be created in a controlled way, not on the fly.
You could use a collection instead but there is no point here, you can just do:
INSERT INTO tgt
SELECT *
FROM emp
WHERE deptno=20;
I'm not sure why you're wrapping that in a function at all; your function is also declared to return a number, but you have no return statement.

dropping a global temporary table

2 Separate questions.
I am using this script to drop a table [SOLVED]
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE_NAME';
DBMS_OUTPUT.PUT_LINE ('Global table TABLE_NAME Dropped');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Global table TABLE_NAME Doesn''t exist.');
END;
/
Is there anyway I can differentiate if table "doesn't exist" or it is being used in some other sessions (in that case it would locked and couldn't be deleted). I am not sure if I can see that table exists in user_tables. I am not fully aware of permissions.
I have added this code now
WHEN OTHERS THEN
i_code := SQLCODE;
v_errm := SUBSTR(SQLERRM, 1, 64);
if i_code = -942 THEN
DBMS_OUTPUT.PUT_LINE ('TABLE_NAME doesn''t exist. Script will continue to create it');
ELSE
DBMS_OUTPUT.PUT_LINE ('Error dropping temporary table. The error code is ' || i_code || '- ' || v_errm);
END IF ;
2. I see . at the end of each procedure like this
END PROCEDURE_NAME;
.
/
sho err;
I just don't understand why . is here. Is it syntax or what?
-- First Truncate temporary table
SQL> TRUNCATE TABLE test_temp1;
-- Then Drop temporary table
SQL> DROP TABLE test_temp1;
Step 1. Figure out which errors you want to trap:
If the table does not exist:
SQL> drop table x;
drop table x
*
ERROR at line 1:
ORA-00942: table or view does not exist
If the table is in use:
SQL> create global temporary table t (data varchar2(4000));
Table created.
Use the table in another session. (Notice no commit or anything after the insert.)
SQL> insert into t values ('whatever');
1 row created.
Back in the first session, attempt to drop:
SQL> drop table t;
drop table t
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use
So the two errors to trap:
ORA-00942: table or view does not exist
ORA-14452: attempt to
create, alter or drop an index on temporary table already in use
See if the errors are predefined. They aren't. So they need to be defined like so:
create or replace procedure p as
table_or_view_not_exist exception;
pragma exception_init(table_or_view_not_exist, -942);
attempted_ddl_on_in_use_GTT exception;
pragma exception_init(attempted_ddl_on_in_use_GTT, -14452);
begin
execute immediate 'drop table t';
exception
when table_or_view_not_exist then
dbms_output.put_line('Table t did not exist at time of drop. Continuing....');
when attempted_ddl_on_in_use_GTT then
dbms_output.put_line('Help!!!! Someone is keeping from doing my job!');
dbms_output.put_line('Please rescue me');
raise;
end p;
And results, first without t:
SQL> drop table t;
Table dropped.
SQL> exec p;
Table t did not exist at time of drop. Continuing....
PL/SQL procedure successfully completed.
And now, with t in use:
SQL> create global temporary table t (data varchar2(4000));
Table created.
In another session:
SQL> insert into t values (null);
1 row created.
And then in the first session:
SQL> exec p;
Help!!!! Someone is keeping from doing my job!
Please rescue me
BEGIN p; END;
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use
ORA-06512: at "SCHEMA_NAME.P", line 16
ORA-06512: at line 1
yes - the engine will throw different exceptions for different conditions.
you will change this part to catch the exception and do something different
EXCEPTION
WHEN OTHERS THEN
here is a reference
http://download.oracle.com/docs/cd/B10501_01/appdev.920/a96624/07_errs.htm
The DECLARE GLOBAL TEMPORARY TABLE statement defines a temporary table for the current connection.
These tables do not reside in the system catalogs and are not persistent.
Temporary tables exist only during the connection that declared them and cannot be referenced outside of that connection.
When the connection closes, the rows of the table are deleted, and the in-memory description of the temporary table is dropped.
For your reference http://docs.oracle.com/javadb/10.6.2.1/ref/rrefdeclaretemptable.html
Down the apache server by running below in putty
cd $ADMIN_SCRIPTS_HOME
./adstpall.sh
Drop the Global temporary tables
drop table t;
This will workout..

Getting error while trying to alter table in an sql block

I create a test.sql file and inside I put:
begin
alter table table1 enable row movement;
alter table table1 shrink space;
end;
/
Is this not allowed? Because I get error:
Encountered the symbol "ALTER" when expecting one of the following:
begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe
You cannot issue DDL as static SQL in a PL/SQL block. If you want to put those commands in a PL/SQL block, you'd need to use dynamic SQL, i.e.
BEGIN
EXECUTE IMMEDIATE 'alter table table1 enable row movement';
EXECUTE IMMEDIATE 'alter table table1 shrink space cascade';
END;
/
It may be easier, however, to just issue consecutive SQL statements rather than issuing a single PL/SQL block.

Resources