How to generate DELETE statements in PL/SQL, based on the tables FK relations? - oracle

Is it possible via script/tool to generate authomatically many delete statements based on the tables fk relations, using Oracle PL/SQL?
In example: I have the table: CHICKEN (CHICKEN_CODE NUMBER) and there are 30 tables with fk references to its CHICKEN_CODE that I need to delete; there are also other 150 tables foreign-key-linked to that 30 tables that I need to delete first.
Is there some tool/script PL/SQL that I can run in order to generate all the necessary delete statements based on the FK relations for me?
(by the way, I know about cascade delete on the relations, but please pay attention: I CAN'T USE IT IN MY PRODUCTION DATABASE, because it's dangerous!)
I'm using Oracle DataBase 10G R2.
Please pay attention to this:
Generate Delete Statement From Foreign Key Relationships in SQL 2008?
Another user has just written it in SQL SERVER 2008, anyone is able to convert to Oracle 10G PL/SQL?
I am not able to... :-(
Please assume that V_CHICKEN and V_NATION are the criteria to select the CHICKEN to delete from the root table: the condition is: "where COD_CHICKEN = V_CHICKEN AND COD_NATION = V_NATION" on the root table.

(My first answer became too long and difficult to edit, and it got Community Wikified, which is really annoying. Here is the latest version of the script.)
This script attempts to perform a cascading delete through recursion. It should avoid infinite loops when there are circular references. But it requires that all circular referential constraints have ON DELETE SET NULL or ON DELETE CASCADE.
CREATE OR REPLACE PROCEDURE delete_cascade(
table_owner VARCHAR2,
parent_table VARCHAR2,
where_clause VARCHAR2
) IS
/* Example call: execute delete_cascade('MY_SCHEMA', 'MY_MASTER', 'where ID=1'); */
child_cons VARCHAR2(30);
parent_cons VARCHAR2(30);
child_table VARCHAR2(30);
child_cols VARCHAR(500);
parent_cols VARCHAR(500);
delete_command VARCHAR(10000);
new_where_clause VARCHAR2(10000);
/* gets the foreign key constraints on other tables which depend on columns in parent_table */
CURSOR cons_cursor IS
SELECT owner, constraint_name, r_constraint_name, table_name, delete_rule
FROM all_constraints
WHERE constraint_type = 'R'
AND delete_rule = 'NO ACTION'
AND r_constraint_name IN (SELECT constraint_name
FROM all_constraints
WHERE constraint_type IN ('P', 'U')
AND table_name = parent_table
AND owner = table_owner)
AND NOT table_name = parent_table; -- ignore self-referencing constraints
/* for the current constraint, gets the child columns and corresponding parent columns */
CURSOR columns_cursor IS
SELECT cc1.column_name AS child_col, cc2.column_name AS parent_col
FROM all_cons_columns cc1, all_cons_columns cc2
WHERE cc1.constraint_name = child_cons
AND cc1.table_name = child_table
AND cc2.constraint_name = parent_cons
AND cc1.position = cc2.position
ORDER BY cc1.position;
BEGIN
/* loops through all the constraints which refer back to parent_table */
FOR cons IN cons_cursor LOOP
child_cons := cons.constraint_name;
parent_cons := cons.r_constraint_name;
child_table := cons.table_name;
child_cols := '';
parent_cols := '';
/* loops through the child/parent column pairs, building the column lists of the DELETE statement */
FOR cols IN columns_cursor LOOP
IF child_cols IS NULL THEN
child_cols := cols.child_col;
ELSE
child_cols := child_cols || ', ' || cols.child_col;
END IF;
IF parent_cols IS NULL THEN
parent_cols := cols.parent_col;
ELSE
parent_cols := parent_cols || ', ' || cols.parent_col;
END IF;
END LOOP;
/* construct the WHERE clause of the delete statement, including a subquery to get the related parent rows */
new_where_clause :=
'where (' || child_cols || ') in (select ' || parent_cols || ' from ' || table_owner || '.' || parent_table ||
' ' || where_clause || ')';
delete_cascade(cons.owner, child_table, new_where_clause);
END LOOP;
/* construct the delete statement for the current table */
delete_command := 'delete from ' || table_owner || '.' || parent_table || ' ' || where_clause;
-- this just prints the delete command
DBMS_OUTPUT.put_line(delete_command || ';');
-- uncomment if you want to actually execute it:
--EXECUTE IMMEDIATE delete_command;
-- remember to issue a COMMIT (not included here, for safety)
END;

The problem is if the top level key column isn't propagated all the way down to the bottom.
If you can do DELETE FROM grandchild WHERE parent_id = :1, it is fine.
If you have to do,
DELETE FROM grandchild
WHERE child_id in (SELECT id FROM child WHERE parent_id = :1)
then going down six or seven deep will give you ugly (and probably slow) queries.
While you said you can't make the constraints CASCADE, can you make them deferrable initally immediate ? That way existing code should not be impacted. Your 'delete' session would make all constraints deferred. Then delete from the parent, delete from the child where the record wasn't in the parent, delete from the grandchild where there's no match in the child etc...

This is a great exercise in developing your PL/SQL skills and general Oracle knowledge!
You need to identify all constrained columns in all tables with relations descending from your master table. You can get all the information you need from two views: ALL_CONSTRAINTS and ALL_CONS_COLUMNS. (If all the tables are in the same schema as the user executing the script, you can use USER_CONSTRAINTS and USER_CONS_COLUMNS if you prefer)
This query will find all the foreign key constraints which refer back to a given table (CUSTOMER in this example):
SELECT constraint_name, table_name, constraint_type
FROM all_constraints
WHERE constraint_type = 'R'
AND r_constraint_name IN (SELECT constraint_name
FROM all_constraints
WHERE constraint_type IN ('P', 'U')
AND table_name = 'CUSTOMER');
CONSTRAINT_NAME C
------------------------------ -
CUSTOMER_FK1 R
CUSTOMER_FK4 R
CUSTOMER_FK5 R
CUSTOMER_FK3 R
CUSTOMER_FK2 R
Now, for each of the results from that query, you can use the CONSTRAINT_NAME column to get a table and column name which you can use to write DELETE statements to delete all child rows in all child tables.
This example gets the table and column name for a constraint called CUSTOMER_FK1
SELECT table_name, column_name
FROM user_cons_columns
WHERE constraint_name = 'CUSTOMER_FK1'
TABLE_NAME COLUMN_NAME
----------------------------- ------------------------------------
RESERVATION CUSTOMER_UID
So you could do, for example:
DELETE FROM reservation
WHERE customer_uid = 00153464
or
DELETE FROM reservation
WHERE customer_uid IN (SELECT customer_uid
FROM customer
WHERE customer_type = 'X')
But your child tables also have child tables, so of course you will have to delete those child rows (call them grandchild rows) first. Supposing there is a table called reservation_detail which has a foreign key relationship with reservation, your delete command for reservation_detail might look like:
DELETE FROM reservation_detail
WHERE reservation_uid in (SELECT reservation_uid
FROM reservation
WHERE customer_uid IN (SELECT customer_uid
FROM customer
WHERE customer_type = 'X')
And if reservation_detail also has children... you get the idea. Of course you could use joins instead of nested queries, but the principle is the same: the more levels deep your dependencies go, the more complex your delete commands become.
So now you know how to do it, the challenge is to write a generic PL/SQL script to delete all child rows, grandchild rows, great-grandchild rows ... (ad infinitum) for any given table, from the bottom up. You will have to employ recursion. Should be a fun program to write!
(Last edit: removed the script; see my other answer for the final solution.)

Related

pl/sql can't modify constraint defferable

I want to make constraint deferrable so I've wrote this code:
alter table life_cycle_phases modify constraint SOME_T_NAME_UNIQUE INITIALLY DEFERRED DEFERRABLE;
but orace returns mistake:
00933. 00000 - "SQL command not properly ended"
what am i doing wrong?
UPDATE:
ok, i see, once i've created not deferrable constraint, i can't check it's state, but i need to!
my problem is: i need to disable all constraints, add row in table,which was random chosen(in fact i don't know in which table the row will be inserted) then enable all constraints.enable novalidate doesn't work, it validates rows, deferrable also doesn't work, what should do?
In general, MODIFY constraint is to just change the state of it. For instance, enable it, disable it..
From docs.. You cannot change the state of a NOT DEFERRABLE constraint to INITIALLY DEFERRED.
Restrictions on Modifying Constraints Modifying constraints is
subject to the following restrictions:
•You cannot change the state of a NOT DEFERRABLE constraint to
INITIALLY DEFERRED.
•If you specify this clause for an index-organized table, then you
cannot specify any other clauses in the same statement.
•You cannot change the NOT NULL constraint on a foreign key column of
a reference-partitioned table, and you cannot change the state of a
partitioning referential constraint of a reference-partitioned table.
So, drop the constraint first and recreate it.
You may use the below to generate the DDL of the constraint.
--For referential integrity constraints.
DBMS_METADATA.GET_DDL('REF_CONSTRAINT',CONSTRAINT_NAME,OWNER)
--For other kinds of constraints.
DBMS_METADATA.GET_DDL('CONSTRAINT',CONSTRAINT_NAME,OWNER)
Or try this.. ( Courtesy : https://gist.github.com/sdeming/869717 ...I haven't tested!!)
select 'alter table ' || source_table || ' add constraint ' || constraint_name || ' foreign key (' || con_columns || ') references ' || target_table || ' (' || ind_columns || ') enable' data
from (select constraint_name, source_table, target_index, target_table, con_columns, wm_concat(column_name) ind_columns
from (select a.constraint_name, a.source_table, a.target_index, b.table_name target_table, a.con_columns, b.column_name, b.column_position
from (select a.constraint_name, a.source_table, a.target_index, wm_concat(a.column_name) con_columns
from (select a.constraint_name,
a.table_name source_table,
a.r_constraint_name target_index,
b.column_name,
b.position
from user_constraints a
inner join user_cons_columns b on (b.constraint_name = a.constraint_name)
where a.constraint_type = 'R'
and a.constraint_name = 'LIFE_CYCLE_PHASES_NAME_UNIQUE'
order by a.constraint_name, b.position) a
group by constraint_name, source_table, target_index) a
inner join user_ind_columns b on (b.index_name = a.target_index)
order by constraint_name, b.column_position)
group by constraint_name, source_table, target_index, target_table, con_columns);
And then DROP it,
alter table OWNER.life_cycle_phases drop constraint LIFE_CYCLE_PHASES_NAME_UNIQUE ;
Finally recreate the constraint only using the generated DDL.

PL/SQL (INSERT/UPDATE) unique constraint violated error in trigger due to sequencel.nextval

I want insert/update records to another table (MICL_SUPERVISORS) using Trigger (pl/sql oracle 10g).
When trigger fired it is giving an error as
ORA-00001: unique constraint violated.
I know it happens because I want to add SUPID from sequence
Select micl_sup_id_seq.nextval into nSUPID from dual
And this is happening inside a loop.
SUPID column is primary key in my table( MICL_SUPERVISOR). So I can't drop that constraint.
Once I tried auto incrementing but it take long time and it didn't work well and it is slow. I have thousands of records in this table. I did it as
SELECT MAX((SUP_ID)+1 from micl_sup_id_seq
Due to this error I did a small research and found out we cannot use seq.nextval inside a trigger. So my question is is there any easy, accurate way to achieve this?
Here is the code (it all happening inside if clause else part is working Fine. Pls note that I have use a cursor , inside open cursor all this happen)
CREATE OR REPLACE TRIGGER "c"."INSERT_MICL_SUP_DETAILS"
AFTER INSERT OR UPDATE OF "ID","SUP_EMP_NO","EMP_NO" ON "MIMAX"."EMP"
REFERENCING OLD AS "OLD" NEW AS "NEW" FOR EACH ROW
DECLARE
miclaim_supervisor_count NUMBER;
employee_company_code VARCHAR2(10);
employee_businessunit NUMBER;
projmgr NUMBER;
nsupid NUMBER;
CURSOR projmgrscursor IS
SELECT b.bu_member_id
FROM bu_member b, emp_sub_div s
WHERE s.emp_no = :NEW.emp_no
AND s.sub_division_code = '0345' AND s.div_code = '1010'
AND b.bu_id IN (SELECT bu_id FROM bu_member WHERE bu_member_id = :NEW.emp_no);
BEGIN
DELETE
FROM micl_supervisors
WHERE emp_no = :NEW.emp_no
AND is_ovverridden = 0;
SELECT count(*)
INTO miclaim_supervisor_count
FROM micl_supervisors
WHERE emp_no = :NEW.emp_no
AND is_ovverridden = 1;
SELECT company_code
INTO employee_company_code
FROM employee_master
WHERE emp_no = :NEW.emp_no;
projmgr := 0;
IF (employee_company_code ='SOFT')THEN
OPEN projmgrscursor;
LOOP
FETCH projmgrscursor INTO projmgr;
EXIT WHEN projmgrscursor%notfound;
SELECT micl_sup_id_seq.nextval INTO nsupid FROM dual;
INSERT INTO micl_supervisors (sup_id,assigned_date
, assigned_by_emp_no
, amount_limit
, is_ovverridden
, sup_emp_no
, rtd_emp
, emp_no)
VALUES ( nsupid
, SYSDATE
, :NEW.entryaddedby_emp_no
, 3000
, 0
, projmgr
, NULL
, :NEW.emp_no);
END LOOP;
CLOSE projmgrscursor;
ELSE
IF(miclaim_supervisor_count IS NULL OR miclaim_supervisor_count<1) THEN
INSERT INTO micl_supervisors VALUES (:NEW.ID
, SYSDATE
, :NEW.entryaddedby_emp_no
, 3000
, 0
, :NEW.sup_emp_no
, NULL
, :NEW.emp_no);
END IF;
END IF;
END;
/
If anything unclear ask me I'll explain furthermore about this scenario , I hope anyone will help to solve this problem
What other constraints are present on the table? It's more likely that you're running into a constraint error other the sequence, which you are fixated upon.
Due to this Error I did a small research and found out we cannot use seq.nextval inside a trigger.
I don't know where you read that, but that's absolutely false. I've used seq.nextval for many of my audit triggers/tables, and it works fine.
Query all_constraints (or user_constraints) with table name micl_supervisors - like so
SELECT *
FROM user_constraints
WHERE table_name = 'MICL_SUPERVISORS'
and update the question or check with what data you're trying to insert.

Update or insert based on if employee exist in table

Do want to create Stored procc which updates or inserts into table based on the condition if current line does not exist in table?
This is what I have come up with so far:
PROCEDURE SP_UPDATE_EMPLOYEE
(
SSN VARCHAR2,
NAME VARCHAR2
)
AS
BEGIN
IF EXISTS(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN)
--what ? just carry on to else
ELSE
INSERT INTO pb_mifid (ssn, NAME)
VALUES (SSN, NAME);
END;
Is this the way to achieve this?
This is quite a common pattern. Depending on what version of Oracle you are running, you could use the merge statement (I am not sure what version it appeared in).
create table test_merge (id integer, c2 varchar2(255));
create unique index test_merge_idx1 on test_merge(id);
merge into test_merge t
using (select 1 id, 'foobar' c2 from dual) s
on (t.id = s.id)
when matched then update set c2 = s.c2
when not matched then insert (id, c2)
values (s.id, s.c2);
Merge is intended to merge data from a source table, but you can fake it for individual rows by selecting the data from dual.
If you cannot use merge, then optimize for the most common case. Will the proc usually not find a record and need to insert it, or will it usually need to update an existing record?
If inserting will be most common, code such as the following is probably best:
begin
insert into t (columns)
values ()
exception
when dup_val_on_index then
update t set cols = values
end;
If update is the most common, then turn the procedure around:
begin
update t set cols = values;
if sql%rowcount = 0 then
-- nothing was updated, so the record doesn't exist, insert it.
insert into t (columns)
values ();
end if;
end;
You should not issue a select to check for the row and make the decision based on the result - that means you will always need to run two SQL statements, when you can get away with one most of the time (or always if you use merge). The less SQL statements you use, the better your code will perform.
BEGIN
INSERT INTO pb_mifid (ssn, NAME)
select SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN);
END;
UPDATE:
Attention, you should name your parameter p_ssn(distinguish to the column SSN ), and the query become:
INSERT INTO pb_mifid (ssn, NAME)
select P_SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = P_SSN);
because this allways exists:
SELECT * FROM tblEMPLOYEE a where a.ssn = SSN

SQL to reset a number of colums to default

I want to write a SQL that resets all colums in a table back to default, except a couple of colums like the primary key.
I just want to name the colums NOT to update, and reset everything else. There are quite many colums in the table, and I dont whant to write:
update my_table set column1 = DEFAULT, column2 = DEFAULT, ... where ...
for all colums, since there are quite many.
Any ideas? I am using Oracle
I don't think there is a procedure to do what you want, but if your only problem is the heavy burden of writing the SQL, you can automate that with ALL_TAB_COLUMNS view. You can improve the idea for your needs:
select
'update ' || TABLE_NAME ||
'set ' ||
COLUMN_NAME || ' = DEFAULT'
from ALL_TAB_COLUMNS
where
table_name = <YOUR_TABLE>

Is there a way in oracle to disable/enable an unnamed constraint?

I want to disable NOT NULL constraints into a table to insert data for test but I can't find a way to disable unnamed constraints.
I found enough info to disable named constraints, but I couldn't find a example to disable unnamed NOT NULL constraint.
I would like to implement this without querying the data dictionary, but... I'm willing to do that if its the only way. But I would like to use a clean ALTER TABLE DDL.
You will need to query the data dictionary, something like this will disable all constraints on the table. Be aware though, that this will disable the constraint system wide, not just for your session.. Perhaps what you really want is to defer the constraint?
drop table testxx
drop table testxx succeeded.
create table testxx ( id number not null )
create table succeeded.
select status from user_constraints where table_name = 'TESTXX'
STATUS
--------
ENABLED
1 rows selected
begin
for cnames in ( select table_name,constraint_name from user_constraints where table_name = 'TESTXX' ) loop
execute immediate 'alter table ' || cnames.table_name || ' disable constraint ' || cnames.constraint_name;
end loop;
end;
anonymous block completed
select status from user_constraints where table_name = 'TESTXX'
STATUS
--------
DISABLED
1 rows selected
You can also just alter the column as follows
create table test_null (col_n number not null);
alter table test_null modify col_n number null;

Resources