How to generate DELETE statements in PL/SQL, based on the tables FK relations?
Posted
by The chicken in the kitchen
on Stack Overflow
See other posts from Stack Overflow
or by The chicken in the kitchen
Published on 2010-04-20T17:02:25Z
Indexed on
2010/04/21
21:13 UTC
Read the original article
Hit count: 266
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.
This is the result I've written, but it is not recursive:
This is a view I have previously written, but of course it is not recursive!
CREATE OR REPLACE FORCE VIEW RUN
(
OWNER_1,
CONSTRAINT_NAME_1,
TABLE_NAME_1,
TABLE_NAME,
VINCOLO
)
AS
SELECT OWNER_1,
CONSTRAINT_NAME_1,
TABLE_NAME_1,
TABLE_NAME,
'('
|| LTRIM (
EXTRACT (XMLAGG (XMLELEMENT ("x", ',' || COLUMN_NAME)),
'/x/text()'),
',')
|| ')'
VINCOLO
FROM ( SELECT CON1.OWNER OWNER_1,
CON1.TABLE_NAME TABLE_NAME_1,
CON1.CONSTRAINT_NAME CONSTRAINT_NAME_1,
CON1.DELETE_RULE,
CON1.STATUS,
CON.TABLE_NAME,
CON.CONSTRAINT_NAME,
COL.POSITION,
COL.COLUMN_NAME
FROM DBA_CONSTRAINTS CON,
DBA_CONS_COLUMNS COL,
DBA_CONSTRAINTS CON1
WHERE CON.OWNER = 'TABLE_OWNER' AND CON.TABLE_NAME = 'TABLE_OWNED'
AND ( (CON.CONSTRAINT_TYPE = 'P')
OR (CON.CONSTRAINT_TYPE = 'U'))
AND COL.TABLE_NAME = CON1.TABLE_NAME
AND COL.CONSTRAINT_NAME = CON1.CONSTRAINT_NAME
--AND CON1.OWNER = CON.OWNER
AND CON1.R_CONSTRAINT_NAME = CON.CONSTRAINT_NAME
AND CON1.CONSTRAINT_TYPE = 'R'
GROUP BY CON1.OWNER,
CON1.TABLE_NAME,
CON1.CONSTRAINT_NAME,
CON1.DELETE_RULE,
CON1.STATUS,
CON.TABLE_NAME,
CON.CONSTRAINT_NAME,
COL.POSITION,
COL.COLUMN_NAME)
GROUP BY OWNER_1,
CONSTRAINT_NAME_1,
TABLE_NAME_1,
TABLE_NAME;
... and it contains the error of using DBA_CONSTRAINTS instead of ALL_CONSTRAINTS...
Please pay attention to this:
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... :-(
This is the code written by another user in SQL SERVER 2008:
DECLARE @COLUMN_NAME AS sysname
DECLARE @TABLE_NAME AS sysname
DECLARE @IDValue AS int
SET @COLUMN_NAME = '<Your COLUMN_NAME here>'
SET @TABLE_NAME = '<Your TABLE_NAME here>'
SET @IDValue = 123456789
DECLARE @sql AS varchar(max) ;
WITH RELATED_COLUMNS
AS (
SELECT QUOTENAME(c.TABLE_SCHEMA) + '.'
+ QUOTENAME(c.TABLE_NAME) AS [OBJECT_NAME]
,c.COLUMN_NAME
FROM PBANKDW.INFORMATION_SCHEMA.COLUMNS AS c WITH (NOLOCK)
INNER JOIN PBANKDW.INFORMATION_SCHEMA.TABLES AS t WITH (NOLOCK)
ON c.TABLE_CATALOG = t.TABLE_CATALOG
AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
AND c.TABLE_NAME = t.TABLE_NAME
AND t.TABLE_TYPE = 'BASE TABLE'
INNER JOIN (
SELECT rc.CONSTRAINT_CATALOG
,rc.CONSTRAINT_SCHEMA
,lkc.TABLE_NAME
,lkc.COLUMN_NAME
FROM PBANKDW.INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
WITH (NOLOCK)
INNER JOIN PBANKDW.INFORMATION_SCHEMA.KEY_COLUMN_USAGE lkc
WITH (NOLOCK)
ON lkc.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
AND lkc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
AND lkc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
INNER JOIN PBANKDW.INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
WITH (NOLOCK)
ON rc.CONSTRAINT_CATALOG = tc.CONSTRAINT_CATALOG
AND rc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
AND rc.UNIQUE_CONSTRAINT_NAME = tc.CONSTRAINT_NAME
INNER JOIN PBANKDW.INFORMATION_SCHEMA.KEY_COLUMN_USAGE rkc
WITH (NOLOCK)
ON rkc.CONSTRAINT_CATALOG = tc.CONSTRAINT_CATALOG
AND rkc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
AND rkc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
WHERE rkc.COLUMN_NAME = @COLUMN_NAME
AND rkc.TABLE_NAME = @TABLE_NAME
) AS j
ON j.CONSTRAINT_CATALOG = c.TABLE_CATALOG
AND j.CONSTRAINT_SCHEMA = c.TABLE_SCHEMA
AND j.TABLE_NAME = c.TABLE_NAME
AND j.COLUMN_NAME = c.COLUMN_NAME
)
SELECT @sql = COALESCE(@sql, '') + 'DELETE FROM ' + [OBJECT_NAME]
+ ' WHERE ' + [COLUMN_NAME] + ' = ' + CONVERT(varchar, @IDValue)
+ CHAR(13) + CHAR(10)
FROM RELATED_COLUMNS
PRINT @sql
Thank to Charles, this is the latest not working release of the software, I have added a parameter with the OWNER because the referential integrities propagate through about 5 other Oracle users (!!!):
CREATE OR REPLACE PROCEDURE delete_cascade (
parent_table VARCHAR2,
parent_table_owner VARCHAR2)
IS
cons_name VARCHAR2 (30);
tab_name VARCHAR2 (30);
tab_name_owner VARCHAR2 (30);
parent_cons VARCHAR2 (30);
parent_col VARCHAR2 (30);
delete1 VARCHAR (500);
delete2 VARCHAR (500);
delete_command VARCHAR (4000);
CURSOR cons_cursor
IS
SELECT constraint_name,
r_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 = parent_table
AND owner = parent_table_owner)
AND delete_rule = 'NO ACTION';
CURSOR tabs_cursor
IS
SELECT DISTINCT table_name
FROM all_cons_columns
WHERE constraint_name = cons_name;
CURSOR child_cols_cursor
IS
SELECT column_name, position
FROM all_cons_columns
WHERE constraint_name = cons_name AND table_name = tab_name;
BEGIN
FOR cons IN cons_cursor
LOOP
cons_name := cons.constraint_name;
parent_cons := cons.r_constraint_name;
SELECT DISTINCT table_name, owner
INTO tab_name, tab_name_owner
FROM all_cons_columns
WHERE constraint_name = cons_name;
delete_cascade (tab_name, tab_name_owner);
delete_command := '';
delete1 := '';
delete2 := '';
FOR col IN child_cols_cursor
LOOP
SELECT DISTINCT column_name
INTO parent_col
FROM all_cons_columns
WHERE constraint_name = parent_cons AND position = col.position;
IF delete1 IS NULL
THEN
delete1 := col.column_name;
ELSE
delete1 := delete1 || ', ' || col.column_name;
END IF;
IF delete2 IS NULL
THEN
delete2 := parent_col;
ELSE
delete2 := delete2 || ', ' || parent_col;
END IF;
END LOOP;
delete_command :=
'delete from '
|| tab_name_owner
|| '.'
|| tab_name
|| ' where ('
|| delete1
|| ') in (select '
|| delete2
|| ' from '
|| parent_table_owner
|| '.'
|| parent_table
|| ');';
INSERT INTO ris
VALUES (SEQUENCE_COMANDI.NEXTVAL, delete_command);
COMMIT;
END LOOP;
END;
/
In the cursor CONS_CURSOR I have added the condition:
AND delete_rule = 'NO ACTION';
in order to avoid deletion in case of referential integrities with DELETE_RULE = 'CASCADE' or DELETE_RULE = 'SET NULL'.
Now I have tried to turn from stored procedure to stored function, but the delete statements are not correct:
CREATE OR REPLACE FUNCTION deletecascade (
parent_table VARCHAR2,
parent_table_owner VARCHAR2)
RETURN VARCHAR2
IS
cons_name VARCHAR2 (30);
tab_name VARCHAR2 (30);
tab_name_owner VARCHAR2 (30);
parent_cons VARCHAR2 (30);
parent_col VARCHAR2 (30);
delete1 VARCHAR (500);
delete2 VARCHAR (500);
delete_command VARCHAR (4000);
AT_LEAST_ONE_ITERATION NUMBER DEFAULT 0;
CURSOR cons_cursor
IS
SELECT constraint_name,
r_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 = parent_table
AND owner = parent_table_owner)
AND delete_rule = 'NO ACTION';
CURSOR tabs_cursor
IS
SELECT DISTINCT table_name
FROM all_cons_columns
WHERE constraint_name = cons_name;
CURSOR child_cols_cursor
IS
SELECT column_name, position
FROM all_cons_columns
WHERE constraint_name = cons_name AND table_name = tab_name;
BEGIN
FOR cons IN cons_cursor
LOOP
AT_LEAST_ONE_ITERATION := 1;
cons_name := cons.constraint_name;
parent_cons := cons.r_constraint_name;
SELECT DISTINCT table_name, owner
INTO tab_name, tab_name_owner
FROM all_cons_columns
WHERE constraint_name = cons_name;
delete1 := '';
delete2 := '';
FOR col IN child_cols_cursor
LOOP
SELECT DISTINCT column_name
INTO parent_col
FROM all_cons_columns
WHERE constraint_name = parent_cons AND position = col.position;
IF delete1 IS NULL
THEN
delete1 := col.column_name;
ELSE
delete1 := delete1 || ', ' || col.column_name;
END IF;
IF delete2 IS NULL
THEN
delete2 := parent_col;
ELSE
delete2 := delete2 || ', ' || parent_col;
END IF;
END LOOP;
delete_command :=
'delete from '
|| tab_name_owner
|| '.'
|| tab_name
|| ' where ('
|| delete1
|| ') in (select '
|| delete2
|| ' from '
|| parent_table_owner
|| '.'
|| parent_table
|| ');'
|| deletecascade (tab_name, tab_name_owner);
INSERT INTO ris
VALUES (SEQUENCE_COMANDI.NEXTVAL, delete_command);
COMMIT;
END LOOP;
IF AT_LEAST_ONE_ITERATION = 1
THEN
RETURN ' where COD_CHICKEN = V_CHICKEN AND COD_NATION = V_NATION;';
ELSE
RETURN NULL;
END IF;
END;
/
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.
© Stack Overflow or respective owner