【问题标题】:Deleting Related Rows in a Many-to-Many Relationship删除多对多关系中的相关行
【发布时间】:2011-12-07 10:26:16
【问题描述】:

我正在删除多对多关系的一个站点上的表中的一行。我还想删除该关系另一端的所有相关行。

例如,假设我有以下表格,我想从Cars 中删除一行。我还想删除 Drivers 中的所有相关行,当然还有 CarDrivers 中不再需要的任何行。

Table Cars:
CarID      int
CarName    nvarchar(100)

Table Drivers:
DriverID   int
DriverName nvarchar(100)

Table CarDrivers:
CarID      int
Driver     int

我知道如何在SELECT 查询中加入上述表格。但我不知道如何删除关系中的数据。

注意:关系的双方都实现了级联删除。因此,例如,从Cars 中删除一行将删除CarDrivers 中的所有相关行。但显然这不会传播到Drivers 表。

【问题讨论】:

标签: sql sql-server sql-server-2008


【解决方案1】:

我的项目也有类似的问题(使用带有 knex 和 postgres 的 node.js)。

然而,我的多对多表外键都是不可为空的,这可能与您的示例不同,因为据推测汽车可以在没有驱动程序的情况下存在于数据库中。

通过将不可为空、限制和级联应用于多对多,我发现从每个表中单独删除,以它们迁移的相反顺序,效果很好。使用 node/knex,我将这些函数导入到其他需要用作回调的文件中,并避免重复。

这可能不是最佳方式,(sub-queries in knex don't read great) 但足以让事情在基线上运行。

【讨论】:

    【解决方案2】:

    在 Oracle 中,您可以使用触发器来处理它,特别是复合触发器

    alter table CarDrivers add CONSTRAINT CarFK FOREIGN KEY (CarID)
          REFERENCES Cars (CarID) on delete cascade enable
    /    
    
    create or replace TRIGGER "CarDrivers_delete"  
    for delete ON CarDrivers 
    compound trigger
      type driver_ids is table of Drivers.DriverID%type INDEX BY PLS_INTEGER;
      ids driver_ids;
    
      AFTER EACH ROW IS    
      BEGIN   
          ids (ids.COUNT + 1) := :NEW.Driver;    
      END AFTER EACH ROW;
    
       AFTER STATEMENT IS    
       BEGIN      
          FOR i IN 1 .. ids.COUNT    
          LOOP                                      
                delete from Drivers
                 WHERE DriverID = ids (i);    
          END LOOP;    
       END AFTER STATEMENT;      
    END;
    /
    

    这样发行就够了

    delete from Cars where CarID = 666
    

    并且删除将通过约束级联到 CarDrivers 表,并通过触发器级联到 Drivers 表。

    必须使用复合触发器来避免 ORA-04091,即变异表错误。从 Oracle11g 开始提供复合触发器。 See here.

    【讨论】:

      【解决方案3】:

      Drivers 和 Cars 表之间没有关系。这种关系是通过 CarDrivers 表实现的。因此,问题仍然存在。

      我知道自动化 CASCADE 删除的唯一方法是删除 CarDrivers 和 Drivers 表之间的 FK,并向 CarDrivers 添加删除之前或之后的触发器,以删除驱动程序中的条目,其中 driver_id 是要删除的行之一在 CarDrivers 中。

      这在很多方面都不干净。如果实际上需要跨连接表进行删除,则关系可能建模错误,并且更清晰的关系应该是简单地将关系建模为“有许多司机的汽车”或 Drivers 表中的汽车 FK .如上所述,对于实际的汽车和司机关系,多对多关系实际上是正确的,您永远不会因为汽车被总计/删除而删除司机。

      【讨论】:

        【解决方案4】:

        你的请求没有意义

        司机作为实体独立于汽车存在。汽车可以由许多司机驾驶,司机可以驾驶许多汽车。这就是为什么你有 many-many 表的原因。

        请注意“司机可以驾驶许多汽车”这一点。这意味着如果您删除 Drivers 行,您需要删除 CarDrivers 中的其他行。

        如果您想要这样做,则需要在 CarDrivers 上触发。从 Drivers 到 CarDrivers 的 CASCADE 将为您删除其他 CarDrivers 行。也不记得触发器递归的默认行为。

        真是一团糟。

        注意:如果您在多对多表中的一列具有唯一性,则此 几乎 是有意义的,那么它应该是 Cars 和 Drivers 之间的外键(Unique on Car 表示“最多每辆车一个司机”是指 Cars 中的 NULLable FK 列)

        【讨论】:

        • Car、Drivers 和 CarDrivers 表只是说明我正在尝试做的一个示例。这就是我正在使用的应用程序所做的。我会按照您描述的方式审查设计,但更改设计可能不取决于我。我认为这与其中一个表链接到其他表并且联结表包含几个附加字段这一事实有关。
        • @Jonathan Wood:如果你需要一张多对多的桌子,这个设计是合理的。这是不合理的要求
        • 进一步看,好像是一对多的关系;但是,使用联结表主要是出于我概述的原因。我可以向你保证,将孤立的项目留在关系的另一端是没有意义的。
        • @Jonathan Wood:然后用所有表格扩展您的问题,以便我们可以正确地批评设计。你给出了一个不能反映现实的坏例子,你会得到不能正确解决问题的坏答案。
        • @Jonathan Wood:我想你当时不明白我的回答:删除 across a many-many is cascading Cars -> CarDrivers -> Drivers -> carDrivers ->司机 -> CarDrivers 等。随意接受 Shark 或等待另一个。
        【解决方案5】:

        如果您有权访问数据库并有权更改表,我只需创建外键并指定 onupdate 和 oncascade :

        ALTER TABLE [dbo].[Drivers]  WITH CHECK ADD  CONSTRAINT [FK__Cars] FOREIGN KEY([CarID])
        REFERENCES [dbo].[Car] ([CarID])
        ON UPDATE CASCADE
        ON DELETE CASCADE
        
        ALTER TABLE [dbo].[CarDrivers]  WITH CHECK ADD  CONSTRAINT [FK_Drivers_Cars] FOREIGN KEY([CarID])
        REFERENCES [dbo].[Car] ([CarID])
        ON UPDATE CASCADE
        ON DELETE CASCADE
        

        这种方法的好处是您无需担心孤立记录。当您从 Car 表中删除一条记录时,其他表中的所有相关信息都会自动删除和更新。您的 SQL 语句也更短。

        【讨论】:

          【解决方案6】:

          我认为最好的方法是您必须先删除相关表的数据。换句话说,如果您想删除汽车和使用该汽车的相应驱动程序,您必须先删除驱动程序,然后再删除汽车。连接表会因为ON CASCADE DELETE删除正确的记录。

          试试这个:

          delete
          from Drivers
          where DriverID in
          (
              select d.DriverID
              from Drivers d
              inner join CarDrivers cd
              on d.DriverID = cd.Driver
              inner join Cars c
              on c.CarID = cd.CarID
              where c.CarID = 1
          )
          
          delete
          from Cars
          where CarID = 1
          

          当然,你不需要在那里硬编码1,如果你在存储过程中使用这个代码sn-p,你可以使用包括参数在内的任何东西。

          【讨论】:

            【解决方案7】:

            将级联删除放在 CarDrivers 表中。

            【讨论】:

            • 那只会删除 CarDrivers 表中的相关数据。我相信 OP 还希望删除作为连接一部分的相应表数据。在这种情况下,级联删除将无法满足需求。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-01-27
            • 2015-08-22
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多