从父表和子表中删除行

Posted

技术标签:

【中文标题】从父表和子表中删除行【英文标题】:Deleting rows from parent and child tables 【发布时间】:2011-03-04 16:06:39 【问题描述】:

假设 Oracle 10G 中有两个表

TableA (Parent) --> TableB (Child)

TableA 中的每一行在 TableB 中都有几个与其相关的子行。

我想删除 TableA 中的特定行,这意味着我必须先删除 tableB 中的相关行。

这会删除子条目

delete from tableB where last_update_Dtm = sysdate-30;

要删除子表中刚刚删除的行的父行,我可以这样做

Delete from TableA where not exists (select 1 from tableB where tableA.key=tableB.key);

以上内容还将删除子表中 (last_update_Dtm = sysdate-30) 为 false 的行。 TableA 没有 last_update_dtm 列,因此如果没有子表中的条目,就无法知道要删除哪些行。

我可以在删除之前将键保存在子表中,但这似乎是一种昂贵的方法。删除两个表中的行的正确方法是什么?

编辑

为了更好地解释我想要实现的目标,如果两个表之间没有约束,以下查询将完成我想要做的事情。

Delete from tableA
Where exists (
Select 1 from tableB
where tableA.key=tableB.key
and tableB.last_update_dtm=sysdate-30)

Delete from tableB where last_update_dtm=systdate-30

【问题讨论】:

请注意,sysdate 的测量精度为 1/100 秒。因此,在 2011-03-04 10:01:32.05 运行删除只会删除时间戳为 2011-02-06 10:01:32.05 的记录。我猜这不是你想要的。我希望您想要更像“如果在过去 30 天内没有更新的孩子,则删除父母”。这更像是“不存在的地方(从 tableB 中选择 1 ... and last_update_dtm > sysdate - 30)”。 【参考方案1】:

两种可能的方法。

    如果您有外键,请将其声明为 on-delete-cascade 并删除超过 30 天的父行。所有子行将被自动删除。

    根据您的描述,您似乎知道要删除的父行并需要删除相应的子行。你试过这样的 SQL 吗?

      delete from child_table
          where parent_id in (
               select parent_id from parent_table
                    where updd_tms != (sysdate-30)
    

    -- 现在删除父表记录

    delete from parent_table
    where updd_tms != (sysdate-30);
    

---- 根据您的要求,您可能必须使用 PL/SQL。我会看看是否有人可以为此发布一个纯 SQL 解决方案(在这种情况下,这肯定是要走的路)。

declare
    v_sqlcode number;
    PRAGMA EXCEPTION_INIT(foreign_key_violated, -02291);
begin
    for v_rec in (select parent_id, child id from child_table
                         where updd_tms != (sysdate-30) ) loop

    -- delete the children
    delete from child_table where child_id = v_rec.child_id;

    -- delete the parent. If we get foreign key violation, 
    -- stop this step and continue the loop
    begin
       delete from parent_table
          where parent_id = v_rec.parent_id;
    exception
       when foreign_key_violated
         then null;
    end;
 end loop;
end;
/

【讨论】:

具有日期值的列在子表中,而不是在父表中。谢谢 那么..对于您删除的每个孩子,您想看看父母是否没有其他孩子,然后删除父母? 是的,我决定使用这种方法,因为它更干净。谢谢 请注意上面的选项 2,使用 (sysdate - 30) 两次是不可靠的,因为每次调用 (sysdate - 30) 都会返回稍微不同的值,因为两次调用之间经过了一段时间。此经过的时间可能足以导致子记录逃脱删除,然后删除父记录将失败。使用 trunk(sysdate -30) 可能会有所帮助,除非您在午夜附近运行代码,在这种情况下,两个调用仍可能返回不同的值。最好在某处的表中找到当前时间戳,并根据该时间戳进行删除,例如(table_x.timestamp_Y - 30)【参考方案2】:

如果子级有 FK 将它们链接到父级,那么您可以在父级上使用 DELETE CASCADE。

例如

CREATE TABLE supplier 
( supplier_id numeric(10) not null, 
 supplier_name varchar2(50) not null, 
 contact_name varchar2(50),  
 CONSTRAINT supplier_pk PRIMARY KEY (supplier_id) 
); 



CREATE TABLE products 
( product_id numeric(10) not null, 
 supplier_id numeric(10) not null, 
 CONSTRAINT fk_supplier 
   FOREIGN KEY (supplier_id) 
  REFERENCES supplier(supplier_id) 
  ON DELETE CASCADE 
); 

删除供应商,它会删除该供应商的所有产品

【讨论】:

是的,但问题是要知道要删除哪个供应商,我必须查看产品表。而且我正在使用的列不是使用“ON DELETE CASCADE”子句创建的。 你能创建 ON DELETE CASCADE 吗?如果是这样,您可以编写一个删除语句,从子表中提取一组聚合键。 感谢 Mark,我最终决定采用 pl/sql 方法,因为它更容易。改变表格的结构也可以,但我必须做更多的测试:) 谢谢【参考方案3】:

这里有一个完整的例子来说明如何做到这一点。 但是,您需要对子表具有闪回查询权限。

这是设置。

create table parent_tab
  (parent_id number primary key,
  val varchar2(20));

create table child_tab
    (child_id number primary key,
    parent_id number,
    child_val number,
     constraint child_par_fk foreign key (parent_id) references parent_tab);

insert into parent_tab values (1,'Red');
insert into parent_tab values (2,'Green');
insert into parent_tab values (3,'Blue');
insert into parent_tab values (4,'Black');
insert into parent_tab values (5,'White');

insert into child_tab values (10,1,100);
insert into child_tab values (20,3,100);
insert into child_tab values (30,3,100);
insert into child_tab values (40,4,100);
insert into child_tab values (50,5,200);

commit;

select * from parent_tab
where parent_id not in (select parent_id from child_tab);

现在删除孩子的子集(父母为 1,3 和 4 - 但不是 5 的孩子)。

delete from child_tab where child_val = 100;

然后从 child_tab 的当前 COMMITTED 状态获取 parent_ids(即它们在您删除之前的状态)并删除您的会话尚未删除的那些。这为您提供了已删除的子集。 然后,您可以将它们从 parent_tab 中删除

delete from parent_tab
where parent_id in
  (select parent_id from child_tab as of scn dbms_flashback.get_system_change_number
  minus
  select parent_id from child_tab);

'Green' 仍然存在(因为它在子表中没有任何条目)并且'Red' 仍然存在(因为它仍然在子表中有条目)

select * from parent_tab
where parent_id not in (select parent_id from child_tab);

select * from parent_tab;

这是一个奇特/不寻常的操作,所以如果我这样做,我可能会有点谨慎,并在事务开始时将子表和父表都锁定为独占模式。此外,如果子表很大,它的性能不会特别好,所以我会选择像 Rajesh 的 PL/SQL 解决方案。

【讨论】:

谢谢加里,我没有使用这种方法,但知道我可以这样做很有用。

以上是关于从父表和子表中删除行的主要内容,如果未能解决你的问题,请参考以下文章

什么是父表和子表

mysql数据库外键删除更新规则

oracle中建父表和子表的语句是啥?

什么是数据库中的父表和子表?

Laravel:使用同步或 updateOrCreate 更新父表和子表?

如何从mysql5中的父表中删除数据?