Oracle 如何基于表FK关系在PL/SQL中生成DELETE语句?

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

是否可以通过脚本/工具使用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吗? 我不能…:-(


请假设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;