如何根据表FK关系在PL / SQL中生成DELETE语句?

时间:2010-04-20 17:02:26

标签: oracle plsql code-generation

是否可以通过脚本/工具使用Oracle PL / SQL基于表fk关系自动生成许多删除语句?

在示例中:我有表:CHICKEN(CHICKEN_CODE NUMBER)并且有30个表格,其中包含我需要删除的CHICKEN_CODE的fk引用;还有其他150个表外键链接到我需要先删除的30个表。

我是否可以运行一些工具/脚本PL / SQL,以便根据FK关系为我生成所有必要的删除语句?

(顺便说一句,我知道有关关系的级联删除,但请注意:我不能在我的生产数据库中使用它,因为它很危险!)

我正在使用Oracle DataBase 10G R2。

请注意:

Generate Delete Statement From Foreign Key Relationships in SQL 2008?

另一位用户刚刚在SQL SERVER 2008中编写过,任何人都可以转换为Oracle 10G PL / SQL? 我无法......: - (

请假设V_CHICKEN和V_NATION是从根表中选择要删除的CHICKEN的条件:条件是:“根表上的COD_CHICKEN = V_CHICKEN和COD_NATION = V_NATION”。

3 个答案:

答案 0 :(得分:21)

(我的第一个答案变得太长而且很难编辑,它得到了社区维基,这真的很烦人。这是脚本的最新版本。)

此脚本尝试通过递归执行级联删除。当存在循环引用时,它应该避免无限循环。但它要求所有循环引用约束都有ON DELETE SET NULLON 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;

答案 1 :(得分:3)

问题是如果顶级键列没有一直传播到底部。 如果你可以从孙子WHERE parent_id =:1中删除它,那很好。 如果你不得不这样做,

DELETE FROM grandchild
WHERE child_id in (SELECT id FROM child WHERE parent_id = :1)

然后深入六或七深会给你丑陋(可能很慢)的查询。

虽然你说你不能制作CASCADE约束,但是你可以让它们最终立即推迟吗?这样就不会影响现有代码。您的“删除”会话将延迟所有约束。然后从父项中删除,从父项中删除记录不在父项中的子项,从子项中删除子项中没有匹配的孙等...

答案 2 :(得分:2)

这是开发PL / SQL技能和一般Oracle知识的一个很好的练习!

您需要在所有表中标识所有受约束的列,其关系从主表开始下降。您可以从两个视图中获取所需的所有信息:ALL_CONSTRAINTSALL_CONS_COLUMNS。 (如果所有表都与执行脚本的用户位于相同的模式中,则可以根据需要使用USER_CONSTRAINTS和USER_CONS_COLUMNS)

此查询将查找引用给定表的所有外键约束(在此示例中为CUSTOMER):

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

现在,对于该查询的每个结果,您可以使用CONSTRAINT_NAME列获取表和列名称,您可以使用该名称来编写DELETE语句以删除所有子表中的所有子行。

此示例获取名为CUSTOMER_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

DELETE FROM reservation
 WHERE customer_uid IN (SELECT customer_uid
                          FROM customer
                         WHERE customer_type = 'X')

但是您的子表也有子表,所以当然您必须先删除那些子行(称之为孙子行)。假设有一个名为reservation_detail的表与预留有外键关系,你对reservation_detail的删除命令可能如下所示:

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')

如果reservation_detail也有孩子......你明白了。当然,您可以使用连接而不是嵌套查询,但原则是相同的:依赖关系越深,删除命令就越复杂。

所以现在你知道怎么做了,面临的挑战是编写一个通用的PL / SQL脚本来删除任何给定表的所有子行,孙子行,曾孙行......(ad infinitum),来自自下而上。您必须使用recursion。应该是一个有趣的程序来写!

(上次编辑:删除了脚本;请参阅我的最终解决方案的其他答案。)