3

我使用的数据库不是 Oracle 或 Postgresql,这意味着我无权访问延迟约束,这意味着约束必须始终有效(而不仅仅是提交时)。

假设我在数据库中存储了一个链表类型结构,如下所示:

id     parentId
---------------
1      null
2      1
3      2
4      3
5      4
6      5

parentId是对 的外键引用id,并且通过约束必须是唯一的。

假设我想将 item 移动5到 item 之前1,所以我们的数据库看起来像这样:

id     parentId
---------------
1      null
2      5 <-- different
3      2
4      3
5      1 <-- different
6      4 <-- different

需要更改三行,即三个更新语句。这些更新语句中的任何一个都将导致违反约束:在约束再次有效之前,所有三个语句都必须完成。

我的问题是:不违反唯一性约束的最佳方法是什么?

我目前可以设想两种不同的解决方案,但我都不喜欢:

  • 每个受影响的设置parentIdnull然后执行三个更新
  • 完全改变我的数据模型,使它更像是一个“写入时复制”风格的版本数据库,这些问题都不是问题。
4

1 回答 1

0

您可以在单个查询中执行此操作。我敢肯定这有很多变化,但这是我会使用的......

DECLARE
  @node_id         INT,
  @new_parent_id   INT
SELECT
  @node_id         = 5,
  @new_parent      = 1

UPDATE
  yourTable
SET
  parent_id = CASE WHEN yourTable.id = target_node.id    THEN new_antiscendant.id
                   WHEN yourTable.id = descendant.id     THEN target_node.parent_id
                   WHEN yourTable.id = new_descendant.id THEN target_node.id
              END
FROM
  yourTable          AS target_node
LEFT JOIN
  yourTable          AS descendant
    ON descendant.parent_id = target_node.id
LEFT JOIN
  yourTable          AS new_antiscendant
    ON new_antiscendant.id = @new_parent_id
LEFT JOIN
  yourTable          AS new_descendant
    ON COALESCE(new_descendant.parent_id, -1) = COALESCE(new_antiscendant.id, -1)
INNER JOIN
  yourTable
    ON yourTable.id IN (target_node.id, descendant.id, new_descendant.id)
WHERE
  target_node.id = @node_id

即使 @new_parent_id 为 NULL 或列表中的最后一条记录,这也将起作用。

MySQL 不喜欢更新中的自联接,因此该方法可能是将 LEFT JOIN 放入临时表中以获取新映射。然后加入该表以在单个查询中更新所有三个记录。

INSERT INTO
  yourTempTable
SELECT
  yourTable.id    AS node_id,
  CASE WHEN yourTable.id = target_node.id    THEN new_antiscendant.id
       WHEN yourTable.id = descendant.id     THEN target_node.parent_id
       WHEN yourTable.id = new_descendant.id THEN target_node.id
  END             AS new_parent_id
FROM
  yourTable          AS target_node
LEFT JOIN
  yourTable          AS descendant
    ON descendant.parent_id = target_node.id
LEFT JOIN
  yourTable          AS new_antiscendant
    ON new_antiscendant.id = @new_parent_id
LEFT JOIN
  yourTable          AS new_descendant
    ON COALESCE(new_descendant.parent_id, -1) = COALESCE(new_antiscendant.id, -1)
INNER JOIN
  yourTable
    ON yourTable.id IN (target_node.id, descendant.id, new_descendant.id)
WHERE
  target_node.id = @node_id

UPDATE
  yourTable
SET
  parent_id = yourTempTable.newParentID
FROM
  yourTable
INNER JOIN
  yourTempTable
    ON yourTempTamp.node_id = yourTable.id

(确切的语法取决于您的 RDBMS。)

于 2012-06-19T12:34:15.843 回答