【问题标题】:What is the right order of insertion/deletion/modification on dataset?数据集上插入/删除/修改的正确顺序是什么?
【发布时间】:2012-04-05 19:30:51
【问题描述】:

The MSDN claims that the order is

  1. 子表:删除记录。
  2. 父表:插入、更新和删除记录。
  3. 子表:插入和更新记录。

我对此有意见。

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

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

如果我们将 child1 更新为具有新的父 parent2,然后我们删除 parent1。

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

那么正确的顺序是什么,MSDN 在这个问题上是否错误?

我个人的想法是

  1. 子表:删除记录。
  2. 父表:插入、更新记录。
  3. 子表:插入和更新记录。
  4. 父表:删除记录。

但问题是,由于可能存在唯一约束,我们必须始终在添加新记录之前删除表中的记录...所以我现在没有将数据提交到数据库的解决方案。

编辑:感谢您的回答,但您的极端情况是我的日常情况......我选择了禁用约束的丑陋解决方案,然后更新数据库并重新启用约束。我仍在寻找更好的解决方案..

【问题讨论】:

  • 这是 ms sql server 吗?还是我错过了标签。有趣的问题 +1
  • 它是 SqlServer 2008,但我认为它非常独立于 DBMS!
  • 我认为孩子改变父母是一种不寻常的情况。

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


【解决方案1】:

听起来像:

  1. 插入 parent2。 Child 仍然指向 parent1。
  2. 更新子节点以指向父节点 2。现在没有任何内容引用 parent1。
  3. 删除 parent1。

您希望将其包装在可用的事务中。

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

  1. 更新 parent1 以指示它已锁定(或将其锁定在 DB 中),从而阻止更新。
  2. 插入父节点2
  3. 更新子节点以指向 parent2
  4. 删除父级1

此顺序的优点是父子节点之间的连接将始终返回一致的结果。当孩子更新连接的结果时,将“翻转”到新状态。

编辑:

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

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;

【讨论】:

    【解决方案2】:

    我认为在表格上分离动作不是一个好的设计,所以我的解决方案是

    1. 插入/更新/删除父表
    2. 插入/更新/删除子表

    关键是您不应该更改子记录的 parentId,您应该删除 parent1 的 child 并将新的 child 添加到 parent2。通过这样做,您将不再担心破坏约束。当然你必须使用事务。

    【讨论】:

    • 想象一个用户表,指向一个国家表。每次用户想要更改他的国家/地区时,我都无法删除用户行...(不要回答我不会删除国家/地区,这不是重点:))
    • 我认为您在更改用户国家/地区时不需要更新国家/地区表。所以你不需要删除用户:)。这种情况只发生在我的主子表上,而不是查找表上。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

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

      在编写客户端数据应用软件的背景下。

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

      • 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. 我们删除了 parent1:我们打破了约束,因为孩子仍然附加到 parent1,除非我们先更新它。

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

      1. 更新子节点以拥有新的父节点,然后提交。无论第一个父母发生什么情况,都必须更正这些数据。具体来说,即使存在并发更新或其他限制导致暂时或永久无法删除第一个父项,也必须更正此数据。 (这不是参照完整性问题,因为 SQL 外键约束中没有 ON DELETE SET TO NEXT PARENT ID OR MAKE YOUR BEST GUESS 子句。)

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

      【讨论】:

        【解决方案5】:

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

        1. 子表(级联删除)
        2. 父表:插入和/或更新和/或删除记录意味着级联删除的最后一步。
        3. 子表:插入或更新。

        既然我们谈到级联删除,我们必须保证通过删除父记录,在删除父记录之前,需要删除任何与父记录相关的子记录。如果我们没有子记录,则子级别没有删除。就是这样。

        另一方面,您可能会以不同的方式处理您的案件。我认为现实生活中的(几乎)场景会更有帮助。假设父表是订单的主要部分(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
        

        第二种选择的原因,顺便说一下,对于许多应用程序来说是更可取的选择,它为每个父记录提供了详细信息 ID 的原始序列。我已经看到通过重新创建所有详细信息记录来扩展第二个选项的案例。我认为很容易找到与此案例相关的开源解决方案并检查实现。

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

        【讨论】:

          【解决方案6】:

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

          如果没有,你可以试试

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

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

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

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

          (a) 对父项的关键约束规定父项 DELETES 必须在父项 INSERTS 之前,
          (b) 对子项的关键约束规定子项 DELETES 必须在子项 INSERTS 之前,
          (c) FK 规定子 DELETES 必须先于父 DELETES
          (d) FK 还规定子 INSERTS 必须遵循父 INSERTS

          给定的序列是唯一可能满足这 4 个要求的序列,并且它还表明对子节点的 UPDATE 无论如何都无法解决,因为 UPDATE 意味着“同时”删除和插入。

          【讨论】:

          • 非常有趣的一点,我从来没有将更新视为插入+更新,但我知道我理解为什么我会陷入死胡同。为了性能,我更喜欢禁用约束,但我保留了将更新分成两部分的想法。
          • 感谢您的回答!我现在正在努力解决类似的问题。您的建议可能对我有用,但是用 DELETE 加 INSERT 替换 UPDATE 可能会产生不必要的副作用,例如级联删除一些孩子。回答完这个问题后你有什么新想法吗?
          • PS 当我说 UPDATE 等同于同时 DELETE+INSERT 时,我的意思是仅在语义意义上(这就是为什么我添加了“但可能没有以可接受的速度”。所描述的方法也假定删除和插入的集合是“最小的”,并且诸如来自级联删除(或来自表上定义的触发器)的副作用违反了该假设。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-04-13
          • 2020-05-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-09
          • 2022-06-29
          相关资源
          最近更新 更多