Oracle 如何基于表FK关系在PL/SQL中生成DELETE语句?
是否可以通过脚本/工具使用Oracle PL/SQL,基于表fk关系自动生成许多delete语句 例如:我有一个表:CHICKEN(CHICKEN_代码编号),有30个表需要删除对其CHICKEN_代码的fk引用;还有另外150个表外键链接到我需要先删除的30个表 是否有一些工具/脚本PL/SQL可以运行,以便根据FK关系为我生成所有必要的delete语句 (顺便说一句,我知道关于关系的级联删除,但请注意:我不能在我的生产数据库中使用它,因为它很危险!) 我正在使用Oracle DataBase 10G R2 请注意: 另一个用户刚刚在SQLServer2008中编写了它,有人能够转换为Oracle 10G PL/SQL吗? 我不能…:-(Oracle 如何基于表FK关系在PL/SQL中生成DELETE语句?,oracle,plsql,code-generation,Oracle,Plsql,Code Generation,是否可以通过脚本/工具使用Oracle PL/SQL,基于表fk关系自动生成许多delete语句 例如:我有一个表:CHICKEN(CHICKEN_代码编号),有30个表需要删除对其CHICKEN_代码的fk引用;还有另外150个表外键链接到我需要先删除的30个表 是否有一些工具/脚本PL/SQL可以运行,以便根据FK关系为我生成所有必要的delete语句 (顺便说一句,我知道关于关系的级联删除,但请注意:我不能在我的生产数据库中使用它,因为它很危险!) 我正在使用Oracle DataBase
请假设V_CHICKEN和V_NATION是从根表中选择要删除的鸡的标准:条件是根表上的“where COD_CHICKEN=V_CHICKEN和COD_NATION=V_NATION”。这是发展PL/SQL技能和一般Oracle知识的一个很好的练习 您需要标识所有表中的所有受约束列,这些列的关系从主表中递减。您可以从两个视图中获取所需的所有信息:和。(如果所有表与执行脚本的用户位于同一架构中,您可以使用用户约束和用户约束列(如果愿意的话) 此查询将查找所有引用给定表的外键约束(本例中为
CUSTOMER
):
现在,对于该查询的每个结果,您可以使用CONSTRAINT\u NAME
列来获取表和列名,您可以使用该表和列名编写DELETE语句来删除所有子表中的所有子行
此示例获取名为CUSTOMER\u FK1
SELECT table_name, column_name
FROM user_cons_columns
WHERE constraint_name = 'CUSTOMER_FK1'
TABLE_NAME COLUMN_NAME
----------------------------- ------------------------------------
RESERVATION CUSTOMER_UID
因此,您可以这样做,例如:
DELETE FROM reservation
WHERE customer_uid = 00153464
或
但是您的子表也有子表,因此您当然必须首先删除这些子行(称为孙行)。假设有一个名为reservation\u detail的表与reservation有外键关系,那么reservation\u detail的delete命令可能如下所示:
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')
如果保留详细信息也有子项,您就明白了。当然,您可以使用联接而不是嵌套查询,但原则是一样的:依赖项的级别越深,删除命令就越复杂
现在您知道了如何做到这一点,挑战是编写一个通用PL/SQL脚本,从下到上删除任何给定表的所有子行、孙行、曾孙行…(无限)。您将不得不使用。编写该程序应该很有趣
(上次编辑:删除了脚本;有关最终解决方案,请参阅我的其他答案。)问题在于顶级键列是否没有向下传播到底部。 如果您可以在parent_id=:1的位置执行从孙子中删除操作,则可以。 如果你必须这样做
DELETE FROM grandchild
WHERE child_id in (SELECT id FROM child WHERE parent_id = :1)
然后深入到六、七个深度会给你带来难看的(可能是缓慢的)查询
虽然您说您不能使约束级联,但您能使它们立即延迟吗?这样现有代码就不会受到影响。您的“删除”会话将使所有约束延迟。然后从父项中删除,从父项中没有记录的子项中删除,从没有记录的子项中删除孩子们的atch等等。(我的第一个答案太长,很难编辑,它被社区维基化了,这真的很烦人。这是最新版本的脚本。)
此脚本尝试通过递归执行级联删除。当存在循环引用时,它应避免无限循环。但它要求所有循环引用约束在删除时设置为NULL或在删除级联时设置为NULL
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;
你建议的是手动实现CASACDE DELETE。我看不出这样做的危险性会降低。不管怎样,关键的问题是:你是要销毁所有的记录还是只销毁一些(一个)记录?我已经用一个脚本更新了我的答案供你尝试。但是没有保证…你知道吗,我认为这并不像我想的那么难。如果我今天有时间的话,我可能能够解决它。我已经用一个新脚本更新了我的答案。它对我很有效,但我怀疑如果你有复杂的关系,可能会有问题。试试看,让我知道我是怎么做的t goes.:)为什么要在“发现模式”下编程?这就像“我不知道这是什么样的数据库,也不知道这些表是如何布局和连接的,我只知道我需要删除其中一个表中的一行”。您没有适合此数据库的数据访问层吗?请帮助!我试着写这个程序,但没有成功。我需要在几天内完成手术。。。我的参考完整性是5页长,A3大小。。。。救命!!当我有时间的时候,我会给你写一些代码。同时,试试谷歌。。。一定有人已经写了这篇文章。我在谷歌上尝试了3到4天,但没有有趣的结果。。。。当然,我总是输入错误的关键字。。。我尝试过:代码、生成、删除语句、引用完整性、dba_约束等等。。。如果你能帮助我,我将不胜感激,因为我无法完成这项任务。同时,我将继续使用Google搜索,我会尝试:oracle、pl/sql、delete、table、data、children、recursive、recursive。我快速浏览了一下,没有看到任何明显的东西。顺便说一句,如果你有很多层次的关系,我上面提供的脚本可能会打开很多游标。根据服务器的设置,您可能会达到OpenU游标限制。我无法设置常量
DELETE FROM grandchild
WHERE child_id in (SELECT id FROM child WHERE parent_id = :1)
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;