禁用/启用REFERENCES的外键约束

huangapple go评论81阅读模式
英文:

Disable/Enable FK for REFERENCES

问题

我正在尝试自动化一个流程,以禁用和启用外键,以便我可以删除“坏数据”。

下面是我们一个应用程序模式的示例。我找到了Tom Kyte的查询,以显示一些表之间的关系。


创建表c(x NUMBER,
CONSTRAINT c_pk PRIMARY KEY(x)
);

创建表c1(x NUMBER,
CONSTRAINT c1_pk PRIMARY KEY(x),
CONSTRAINT c1_fk FOREIGN KEY (x) REFERENCES c(x));

创建表c2(x NUMBER,
CONSTRAINT c2_pk PRIMARY KEY(x),
CONSTRAINT c2_fk FOREIGN KEY (x) REFERENCES c2(x));

创建表c3(x NUMBER,
CONSTRAINT c3_pk PRIMARY KEY(x),
CONSTRAINT c3_fk FOREIGN KEY (x) REFERENCES c2(x));

创建表c4(x NUMBER,
CONSTRAINT c4_pk PRIMARY KEY(x),
CONSTRAINT c4_fk FOREIGN KEY (x) REFERENCES c2(x));

 /* 子/父关系 */
with cte as (
  select table_name, constraint_type, constraint_name, r_constraint_name,
    max(decode(constraint_type,'R',1,0)) over(partition by table_name) is_r
  from user_constraints 
  where constraint_type in ('P', 'U', 'R')
)
, child_parent as (
  select distinct s.table_name child, d.table_name parent, d.constraint_type
  from (select * from cte where constraint_type = 'R' or is_r = 0) s
  left join cte d
    on s.r_constraint_name = d.constraint_name
    and s.table_name != d.table_name
)
select level lvl, child, parent, constraint_type
from child_parent
start with parent is null
connect by parent = prior child
order siblings by parent, child;

LVL	CHILD	PARENT	CONSTRAINT_TYPE
1	C	 - 	 - 
2	C1	C	P
1	C2	 - 	 - 
2	C3	C2	P
2	C4	C2	P

我有以下代码,它将禁用和启用FK。我知道这可以合并成一个代码片段(过程,接受参数???),但我还没有到达那一点。

从下面的输出中可以看到,它禁用和启用模式中的所有FK,而我只想要能针对一组引用执行此操作。

例如,如果我传入表“C”,我的代码应该只禁用和启用FK C1_FK。如果我传入表“C2”,我的代码应该只禁用和启用C3_FK,C4_FK。

任何帮助将不胜感激。感谢所有回答的人。请注意,如果有更好的编码此解决方案的方法,我愿意接受所有建议。


/* 禁用外键 */

DECLARE 
  sql_command        varchar2(500);
BEGIN
    FOR r IN (
        SELECT '"' || a.owner
                    || '"."'
                    || a.table_name
                    || '"' AS full_table_name,
                a.constraint_name
            FROM user_constraints a
            JOIN user_constraints b
                ON ( a.r_constraint_name = b.constraint_name
                    AND a.r_owner = b.owner )
            WHERE a.constraint_type = 'R'
                AND a.status = 'ENABLED'
            ORDER BY 1 )  
    LOOP    
        sql_command := 'alter table ' || r.full_table_name || ' disable constraint ' || r.constraint_name;
                       dbms_output.put_line(sql_command);
     execute immediate sql_command;      
    END LOOP ;
END ;
/

Statement processed.
alter table "XXX"."C1" disable constraint C1_FK
alter table "XXX"."C2" disable constraint C2_FK
alter table "XXX"."C3" disable constraint C3_FK
alter table "XXX"."C4" disable constraint C4_FK

DECLARE 
  sql_command        varchar2(500);
BEGIN
    FOR r IN (
        SELECT '"' || a.owner
                    || '"."'
                    || a.table_name
                    || '"' AS full_table_name,
                a.constraint_name
            FROM user_constraints a
            JOIN user_constraints b
                ON ( a.r_constraint_name = b.constraint_name
                    AND a.r_owner = b.owner )
            WHERE a.constraint_type = 'R'
                AND a.status = 'DISABLED'
            ORDER BY 1 )  
    LOOP    
        sql_command := 'alter table ' || r.full_table_name || ' enable constraint ' || r.constraint_name;
                       dbms_output.put_line(sql_command);
     execute immediate sql_command;      
    END LOOP ;
END ;
/

Statement processed.
alter table "XXX"."C1" enable constraint C1_FK
alter table "XXX"."C2" enable constraint C2_FK
alter table "XXX"."C3" enable constraint C3_FK
alter table "XXX"."C4" enable constraint

<details>
<summary>英文:</summary>

I am trying to automate a process to disable and enable foreign keys so I can delete &quot;bad data&quot;.

Below is an example of one of our application schemas. I found this Tom Kyte query to show the relationships between some tables.

create table c ( x NUMBER,
CONSTRAINT c_pk PRIMARY KEY(x)
);

create table c1 ( x NUMBER,
CONSTRAINT c1_pk PRIMARY KEY(x),
CONSTRAINT c1_fk FOREIGN KEY (x) REFERENCES c(x));

create table c2 ( x NUMBER,
CONSTRAINT c2_pk PRIMARY KEY(x),
CONSTRAINT c2_fk FOREIGN KEY (x) REFERENCES c2(x));

create table c3 ( x NUMBER,
CONSTRAINT c3_pk PRIMARY KEY(x),
CONSTRAINT c3_fk FOREIGN KEY (x) REFERENCES c2(x));

create table c4 ( x NUMBER,
CONSTRAINT c4_pk PRIMARY KEY(x),
CONSTRAINT c4_fk FOREIGN KEY (x) REFERENCES c2(x));

/* child/parent relationship */
with cte as (
select table_name, constraint_type, constraint_name, r_constraint_name,
max(decode(constraint_type,'R',1,0)) over(partition by table_name) is_r
from user_constraints
where constraint_type in ('P', 'U', 'R')
)
, child_parent as (
select distinct s.table_name child, d.table_name parent, d.constraint_type
from (select * from cte where constraint_type = 'R' or is_r = 0) s
left join cte d
on s.r_constraint_name = d.constraint_name
and s.table_name != d.table_name
)
select level lvl, child, parent, constraint_type
from child_parent
start with parent is null
connect by parent = prior child
order siblings by parent, child;

LVL CHILD PARENT CONSTRAINT_TYPE
1 C - -
2 C1 C P
1 C2 - -
2 C3 C2 P
2 C4 C2 P


I have the following code, which will disable and enable FK&#39;s. I know this can be merged into one piece of code(procedure, which accepts parameter???) but I haven&#39;t gotten to that point yet.

As you can see from the output below it disables and enables ALL FK&#39;S in the schema where I only want code that will do it for one set of references.

For example, if I pass in table &quot;C&quot; my code should only disable and enable FK C1_FK. If I pass in table &quot;C2&quot; my code should only disable and enable C3_FK, C4_FK

Any help would be greatly appreciated. Thanks in advance to all who answer. Note if there is a better way to code this solution I&#39;m open to all suggestions 

/* disable foreign keys */

DECLARE
sql_command varchar2(500);
BEGIN
FOR r IN (
SELECT '"' || a.owner
|| '"."'
|| a.table_name
|| '"' AS full_table_name,
a.constraint_name
FROM user_constraints a
JOIN user_constraints b
ON ( a.r_constraint_name = b.constraint_name
AND a.r_owner = b.owner )
WHERE a.constraint_type = 'R'
AND a.status = 'ENABLED'
ORDER BY 1 )
LOOP
sql_command := 'alter table ' || r.full_table_name || ' disable constraint ' || r.constraint_name;
dbms_output.put_line(sql_command);
execute immediate sql_command;
END LOOP ;
END ;
/

Statement processed.
alter table "XXX"."C1" disable constraint C1_FK
alter table "XXX"."C2" disable constraint C2_FK
alter table "XXX"."C3" disable constraint C3_FK
alter table "XXX"."C4" disable constraint C4_FK

DECLARE
sql_command varchar2(500);
BEGIN
FOR r IN (
SELECT '"' || a.owner
|| '"."'
|| a.table_name
|| '"' AS full_table_name,
a.constraint_name
FROM user_constraints a
JOIN user_constraints b
ON ( a.r_constraint_name = b.constraint_name
AND a.r_owner = b.owner )
WHERE a.constraint_type = 'R'
AND a.status = 'DISABLED'
ORDER BY 1 )
LOOP
sql_command := 'alter table ' || r.full_table_name || ' enable constraint ' || r.constraint_name;
dbms_output.put_line(sql_command);
execute immediate sql_command;
END LOOP ;
END ;
/

Statement processed.
alter table "XXX"."C1" enable constraint C1_FK
alter table "XXX"."C2" enable constraint C2_FK
alter table "XXX"."C3" enable constraint C3_FK
alter table "XXX"."C4" enable constraint




</details>


# 答案1
**得分**: 1

如果你尝试从父表中删除行,你不能禁用并重新启用指向它的外键。通常情况下,仍然会有子表中指向(现在缺失的)父行的行。

因此,你需要:

- 也从子表中删除/将它们的外键列设置为 `null`
- 使用 `novalidate` 选项重新启用外键

如果你试图从父表中删除的是“坏数据”,你几乎肯定也想从子表中删除它。在这种情况下,你需要在这些子表上运行删除/更新。

这使得启用/禁用外键的过程变得相当多余。只需通过从子表到父表运行 DML,它就可以“正常工作”,同时保持启用外键。

如果你想让这个过程更容易,你可以将外键声明为 `on delete [cascade | set null]`。这会自动将父表上的任何删除传播到子表。

**非常小心设置 `on delete cascade`。使用它很容易在小的查找表上运行 `delete`,然后删除大量数据!**

例如:

```sql
create table par1 ( c1 int constraint p1_pk primary key );
create table par2 ( c2 int constraint p2_pk primary key );

create table chld (
  c1 
    constraint chld_fk_1 references par1 
    on delete cascade,
  c2 
    constraint chld_fk_2 references par2
    on delete set null
);

insert into par1 values ( 1 );
insert into par1 values ( 2 );
insert into par2 values ( 1 );
insert into par2 values ( 2 );

insert into chld values ( 1, 1 );
insert into chld values ( 2, 2 );

select * from chld;
/*
        C1         C2
---------- ----------
         1          1
         2          2
*/

commit;

delete par1 where c1 = 1;
delete par2 where c2 = 2;

select * from chld;
/*
        C1         C2
---------- ----------
         2 <null>    
*/
```

<details>
<summary>英文:</summary>

If you&#39;re trying to delete rows from a parent table you can&#39;t disable &amp; re-enable the foreign keys to it. In general there will still be rows in the child tables pointing to the (now missing) parent row.

So either you need to 

* Also delete from the child tables/set their FK columns to `null`
* Reenable the foreign keys with the `novalidate` option

If it&#39;s &quot;bad data&quot; you&#39;re trying to remove from the parent, you almost certainly want to remove it from the children as well. In which case you need to run deletes/updates on these child tables too.

Which makes the process of enabling/disabling the FKs a rather wasted effort. Just run the DML through the tables from the children up to the parents and it &quot;just works&quot; while leaving the FKs enabled.

If you want to make this process easier, you can declare the foreign keys as `on delete [cascade | set null]`. This automatically propagates any deletes from the parent table to the children.

**Be very careful setting `on delete cascade`. With this it&#39;s very easy to run a `delete` on a small lookup table and end up wiping out huge amounts of data!**

For example:

    create table par1 ( c1 int constraint p1_pk primary key );
    create table par2 ( c2 int constraint p2_pk primary key );
    
    create table chld (
      c1 
        constraint chld_fk_1 references par1 
        on delete cascade,
      c2 
        constraint chld_fk_2 references par2
        on delete set null
    );
    
    insert into par1 values ( 1 );
    insert into par1 values ( 2 );
    insert into par2 values ( 1 );
    insert into par2 values ( 2 );
    
    insert into chld values ( 1, 1 );
    insert into chld values ( 2, 2 );
    
    select * from chld;
    /*
            C1         C2
    ---------- ----------
             1          1
             2          2
    */
    
    commit;
    
    delete par1 where c1 = 1;
    delete par2 where c2 = 2;
    
    select * from chld;
    /*
            C1         C2
    ---------- ----------
             2 &lt;null&gt;    
    */

</details>



huangapple
  • 本文由 发表于 2023年5月13日 21:34:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76242999.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定