数据集上插入/删除/修改的正确顺序是什么?

时间:2012-03-21 09:40:03

标签: c# .net sql dataset foreign-keys

The MSDN claims that the order is

  1. 子表:删除记录。
  2. 父表:插入,更新和删除记录。
  3. 子表:插入和更新记录。
  4. 我遇到了问题。

    示例:ParentTable有两条记录parent1(Id:1)和parent2(Id:2)

    ChildTable有一个记录child1(Id:1,ParentId:1)

    如果我们更新child1以获得新的父parent2,然后我们删除parent1。

    1. 我们在子表中没有删除任何内容
    2. 我们删除了parent1:我们打破了约束,因为子节点仍然附加到parent1,除非我们先更新它。
    3. 那么正确的顺序是什么,并且主题上的MSDN是否为假?

      我的个人想法是

      1. 子表:删除记录。
      2. 父表:插入,更新记录。
      3. 子表:插入和更新记录。
      4. 父表:删除记录。
      5. 但问题是,由于潜在的唯一约束,我们必须在添加新内容之前删除表中的记录...所以我现在没有解决方法将数据提交到我的数据库。

        编辑:感谢您的答案,但您的角落案例是我的日常案例......我选择了丑陋的解决方案来禁用约束,然后更新数据库,并重新启用约束。我还在寻找更好的解决方案..

6 个答案:

答案 0 :(得分:4)

您必须考虑他们的背景。 MS说

  

更新数据集中的相关表时,更新很重要   以适当的顺序减少违反参照的机会   完整性约束。

在编写客户端数据应用软件的上下文中。

为什么减少违反参照完整性约束的可能性很重要?因为违反这些限制意味着

  • dbms和客户端之间的更多往返,要么是客户端代码处理约束违规,要么是人类用户处理违规行为,
  • 花更多的时间,
  • 服务器上的负载更多,
  • 更多人为错误的机会,
  • 更多机会进行并发更新以更改基础数据(可能会混淆应用程序代码,人类用户或两者兼而有之)。

为什么他们认为他们的程序 正确?因为它提供了一个单一的进程,可以避免几乎所有常见情况下的参照完整性违规,甚至在很多不常见的情况下。例如 。 。

  • 如果更新是对引用表的DELETE操作,并且引用表中的外键声明为ON DELETE CASCADE,那么最好的方法是简单地删除引用的行(父行),让dbms管理级联。 (这对于ON DELETE SET DEFAULT和ON DELETE SET NULL也是最佳选择。)

  • 如果更新是对引用表的DELETE操作,并且引用表中的外键被声明为ON DELETE RESTRICT,那么最好的方法是首先删除所有引用行(子行),然后删除引用的行。

但是,通过正确使用事务,MS的过程无论如何都会使数据库保持一致状态。它的价值在于它是一个代码和维护的单一客户端进程,即使它并非在所有情况下都是最优的。 (在软件设计中经常出现这种情况 - 选择一种在所有情况下都不是最佳的方式.ActiveRecord会想到。)

你说

  

示例:ParentTable有两个记录parent1(Id:1)和parent2(Id   :2)

     

ChildTable有一个记录child1(Id:1,ParentId:1)

     

如果我们更新child1以拥有一个新的父parent2和我们   删除parent1。

     
      
  1. 我们在子表中没有删除任何内容
  2.   
  3. 我们删除了parent1:我们打破了约束,因为子节点仍然附加到parent1,除非我们先更新它。
  4.   

这不是参照完整性问题;这是一个程序问题。这个问题显然需要两笔交易。

  1. 更新子项以获得新父项,然后提交。无论第一个父母发生什么情况,都必须更正此数据。具体而言,即使存在并发更新或其他约束使得暂时或永久不可能删除第一个父项,也必须更正此数据。 (这不是引用完整性问题,因为在SQL外键约束中没有ON DELETE SET TO NEXT PARENT ID或使您的BEST GUESS子句。)

  2. 删除第一个父级,然后提交。这可能需要首先更新任意数量的表中的任意数量的子行。在一个庞大的组织中,我可以想象一些像这样的删除需要数周才能完成。

答案 1 :(得分:4)

您的SQL产品不支持延迟约束检查吗?

如果没有,你可以尝试

删除所有子记录 - 删除所有父记录 - 插入所有父记录 - 插入所有子记录

其中任何UPDATE已被拆分为其组成的DELETE和INSERT。

这应该在所有情况下都能正常工作,但速度可接受,可能没有......

同样可以证明,这是唯一可以在所有情况下正常工作的方案,因为:

(a)父母的关键约束要求父DELETES必须在父INSERTS之前,
(b)对儿童的主要限制要求儿童DELETES必须先于儿童INSERTS,
(c)FK规定子DELETES必须在父DELETES之前 (d)FK还规定儿童INSERTS必须遵循父母INSERTS

给定的序列是唯一可以满足这4个要求的序列,并且它还表明,无论如何,子节点的UPDATE都不可能解决,因为UPDATE意味着“同时”DELETE加INSERT。

答案 2 :(得分:3)

听起来像是:

  1. 插入parent2。孩子仍然指向parent1。
  2. 更新child以指向parent2。现在没有任何内容引用parent1。
  3. 删除parent1。
  4. 您希望将其包装在可用的交易中。

    根据您的架构,您还可以将其扩展为:

    1. 更新parent1以指示它已被锁定(或将其锁定在数据库中),从而阻止更新。
    2. 插入parent2
    3. 将孩子更新为指向parent2
    4. 删除parent1
    5. 此订单的优点是父级和子级之间的连接将始终返回一致的结果。当孩子更新时,联接的结果将“翻转”到新状态。

      修改

      另一种选择是将父/子引用移动到另一个表中,例如“链接”;

      CREATE TABLE links (
          link_id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
          parent_id INT NOT NULL,
          child_id INT NOT NULL
      );
      

      您可能希望在父列和子列上使用外键约束,当然还有一些适当的索引。这种安排允许父表和子表之间非常灵活的关系 - 可能过于灵活,但这取决于您的应用程序。现在你可以做类似的事情了。

      UPDATE links
          SET parent_id = @new_parent_id
          WHERE parent_id = @old_parent_id
          AND child_id = @child_id;
      

答案 3 :(得分:1)

删除父记录而不删除子记录的需要是不寻常的,我确信MS定义的正常规定的数据集操作顺序在这种情况下不适用。

最有效的方法是更新子记录以反映新父级,然后删除原始父级。正如其他人所提到的,此操作应在事务中执行。

答案 4 :(得分:0)

我认为对表格进行分离并不是一个好的设计,所以我的解决方案是

  1. 插入/更新/删除父表
  2. 插入/更新/删除子表
  3. 关键是你不应该更改子记录的parentId,你应该删除parent1的子节点并将一个新的子节点添加到parent2。通过这样做你将不再担心破坏约束。当然,你必须使用交易。

答案 5 :(得分:0)

MSDN声明在使用依赖项(外键)的基础上是正确的。将订单视为

  1. 子表(级联删除)
  2. 父表:插入和/或更新和/或删除记录意味着级联删除的最后一步。
  3. 子表:插入或更新。
  4. 由于我们讨论了级联删除,我们必须保证通过删除父记录,在删除父记录之前需要删除与父级相关的任何子记录。如果我们没有子记录,则在子级别没有删除。就是这样。

    另一方面,您可能会以不同的方式接近您的情况。我认为现实生活(几乎)会更有帮助。假设父表是订单的主要部分(orderID,clientID等),子表是详细信息部分(detailID,orderID,productOrServiceID等)。所以你得到一个订单,你有以下

    父表

    orderID = 1 (auto increment)
    ...
    

    儿童表

    detailID = 1 (auto increment)
    orderID = 1
    productOrServiceID = 342
    
    and
    
    detailID = 2
    orderID = 1
    productOrServiceID = 169
    
    and
    
    detailID = 3
    orderID = 1
    productOrServiceID = 307
    

    所以我们有三个产品/服务的订单。现在,您的客户希望您将第二个产品或服务转移到新订单并稍后交付。您有两种选择。

    第一个(直接)

    • 创建一个获得orderID = 2

    • 的新订单(新的父记录)
    • 通过设置orderID = 2更新子表,其中orderID = 1且productOrServiceID = 169

    结果你会有

    父表

    orderID = 1 (auto increment)
    ...
    
    and
    
    orderID = 2
    ...
    

    儿童表

    detailID = 1 (auto increment)
    orderID = 1
    productOrServiceID = 342
    
    and
    
    detailID = 2
    orderID = 2
    productOrServiceID = 169
    
    and
    
    detailID = 3
    orderID = 1
    productOrServiceID = 307
    

    第二个(间接)

    • 将子表中第二个产品/服务的DataRow作为变量保存

    • 从子表中删除相对行

    • 创建一个获得orderID = 2

    • 的新订单(新的父记录)
    • 通过将字段orderID从1更改为2来将保留的DataRow插入子表

    结果你会有

    父表

    orderID = 1 (auto increment)
    ...
    
    and
    
    orderID = 2
    ...
    

    儿童表

    detailID = 1 (auto increment)
    orderID = 1
    productOrServiceID = 342
    
    and
    
    detailID = 3
    orderID = 1
    productOrServiceID = 307
    
    and
    
    detailID = 4
    orderID = 2
    productOrServiceID = 169
    

    第二个选项的原因是许多应用程序的首选选项,它为每个父记录提供了详细信息的原始序列。我已经看到通过重新创建所有细节记录来扩展第二个选项的情况。我认为很容易找到与此案例相关的开源解决方案,并检查实施情况。

    最后,我的个人建议是避免使用数据集执行此类操作,除非您的应用程序是单个用户。数据库可以通过事务以线程安全的方式轻松处理这个“问题”。