(Câu trả lời đầu tiên của tôi trở nên quá dài và khó chỉnh sửa và nó đã bị Community Wikified, điều này thực sự khó chịu. Đây là phiên bản mới nhất của tập lệnh.)
Tập lệnh này cố gắng thực hiện xóa theo tầng thông qua đệ quy. Nó nên tránh các vòng lặp vô hạn khi có các tham chiếu vòng tròn. Nhưng nó yêu cầu tất cả các ràng buộc tham chiếu vòng tròn phải có ON DELETE SET NULL
hoặc 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;