【问题标题】:How to generate DELETE statements in PL/SQL, based on the tables FK relations?如何根据表 FK 关系在 PL/SQL 中生成 DELETE 语句?
【发布时间】:2011-02-10 05:58:41
【问题描述】:

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

例如:我有一个表:CHICKEN (CHICKEN_CODE NUMBER) 并且有 30 个表,我需要删除对它的 CHICKEN_CODE 的 fk 引用;还有其他 150 个表外键链接到我需要首先删除的 30 个表。

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

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

我正在使用 Oracle 数据库 10G R2。

请注意:

Generate Delete Statement From Foreign Key Relationships in SQL 2008?

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

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

【问题讨论】:

  • 您提议的是手动实现 CASACDE DELETE。我看不出这怎么能不那么危险。无论如何,至关重要的问题是:您是要删除 CHICKEN 中的 所有 记录还是只删除一些(一个)?
  • 我已经用脚本更新了我的答案,供您尝试。虽然没有保证......
  • 你知道吗,我认为这并不像我想象的那么难。如果我今天有时间,我也许可以解决它。
  • 我已经用一个新脚本更新了我的答案。它对我有用,但我怀疑如果你有复杂的关系可能会出现问题。试一试,让我知道它是怎么回事。 :)
  • 为什么要在“发现模式”下编程?就像“我不知道这是什么类型的数据库,也不知道表的布局和连接方式,我只知道我需要删除其中的一行”。您没有为此数据库提供适当的数据访问层吗?

标签: oracle plsql code-generation


【解决方案1】:

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

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

【讨论】:

  • 鸡:如果这有任何帮助,我将不胜感激;甚至可以接受答案? :) (关于 this 答案,因为我无法为第一个获得任何代表,因为它现在是社区 wiki)。
  • 我希望我能看到你的数据库,这样我就可以(也许)让这个东西为你工作!
  • 1556 个字符,哇 :) 它有多少层(语句末尾有多少个“)”)?我从中学到了重要的一课……如果我设计了一个大型数据库,我想我会让所有的约束都可以推迟。 :)
  • 如果表很大,那些长(6 级)删除可能需要很长时间才能运行(当然取决于存在哪些索引)。您是否在生产数据库上运行过删除操作?
  • 我建议现在编辑您的问题以使其更简单 - 也许删除代码示例并留下问题文本,以便将来阅读它的任何人都不会感到困惑。
【解决方案2】:

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

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

然后再往下走六到七层会给你带来丑陋(而且可能很慢)的查询。

虽然您说您不能使约束 CASCADE,但您可以使它们最初立即可延迟吗?这样现有的代码不应该受到影响。您的“删除”会话将推迟所有约束。然后从父项中删除,从父项中没有记录的子项中删除,从子项中没有匹配项的孙子项中删除等...

【讨论】:

  • 我无法进行 CASCADE 约束,因为我无法修改数据库:我只能 INSERT、DELETE、SELECT 和 UPDATE,而无需使用数据定义语言语句修改数据库。
【解决方案3】:

这是一个很好的锻炼你的 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 具有外键关系,您对 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 脚本来删除任何给定表的所有子行、孙行、曾孙行...(无限),从自下而上。您将不得不雇用recursion。应该是一个有趣的程序编写!

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

【讨论】:

  • 请帮忙!我试图编写程序但没有成功。我需要在几天内完成该程序...我的参考完整性是 5 页长,A3 大小....帮助!!
  • 有时间我会写一些代码给你。同时,试试谷歌...一定有人已经写过这个了。
  • 我尝试使用 Google 已经 3 或 4 天没有有趣的结果....当然我总是输入错误的关键字...我尝试过:代码、生成、删除语句、引用完整性、dba_constraints 等等……如果你能帮助我,我将不胜感激,因为我无法完成这项任务。在此期间,我将继续使用 Google 进行搜索。
  • 我会尝试:oracle、pl/sql、delete、table、data、children、recursive、recursion。我快速浏览了一下,但没有看到任何明显的东西。
  • 顺便说一下,如果你的关系有很多层次,我上面提供的脚本可能会打开很多游标。根据您服务器的设置,您可能会达到 OPEN_CURSORS 限制。
猜你喜欢
  • 2017-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
相关资源
最近更新 更多