在没有延迟约束的情况下工作的正确模式是什么?

时间:2012-06-19 11:28:51

标签: sql unique-constraint

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

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

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

parentIdid的外键引用,并且必须通过约束才是唯一的。

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

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

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

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

我现在可以设想两种不同的解决方案,我都不喜欢:

  • 将每个受影响的parentId设置为null然后执行三次更新
  • 完全改变我的数据模型,因此它更像是一个“拷贝写入”式版本化数据库,这些问题不是问题。

1 个答案:

答案 0 :(得分: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转换为临时表以获取新映射。然后加入该表以在单个查询中更新所有三个recor。

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。)