PDO 中的级联数据更新
Cascade data update in PDO
我有一个 MySql table 包含使用 modified preorder tree traversal 方法排序的节点。
每个节点都有一个id,一个左值和一个右值。
+---------+-----+-----+
| id | lft | rgt |
+---------+-----+-----+
| root | 1 | 20 |
| father1 | 2 | 7 |
| child1 | 3 | 4 |
| child2 | 5 | 6 |
| father2 | 8 | 9 |
| father3 | 10 | 17 |
| child3 | 11 | 12 |
| child4 | 13 | 14 |
| child5 | 15 | 16 |
| father4 | 18 | 19 |
+---------+-----+-----+
我的目标是在father3
之后带上father1
,得到这个结果。
+---------+-----+-----+
| id | lft | rgt |
+---------+-----+-----+
| root | 1 | 20 |
| father2 | 2 | 3 |
| father3 | 4 | 11 |
| child3 | 5 | 6 |
| child4 | 7 | 8 |
| child5 | 9 | 10 |
| father1 | 12 | 17 |
| child1 | 13 | 14 |
| child2 | 15 | 16 |
| father4 | 18 | 19 |
+---------+-----+-----+
要执行此操作,我需要:
- 增加
father1
(和后代)10
的左右值
- 左右递减
father2
和father3
(以及后代)
6
的值
我正在尝试使用 PDO 执行此更新。
$left = 2; // father1 left value
$right = 7; // father1 right value
$drop = 18; // insert point value (will be after new father1 position)
$delta = $right - $left + 1; // 6
$gap = $drop - $right - 1; // 10
$dbms->beginTransaction();
// increment father1 (and descendants)
$dbms->prepare("UPDATE categories SET lft=lft+:gaplft, rgt=rgt+:gaprgt WHERE lft>=:startlft AND lft<:endlft;");
$dbms->bindparam(':gaplft', $gap, PDO::PARAM_INT);
$dbms->bindparam(':gaprgt', $gap, PDO::PARAM_INT);
$dbms->bindparam(':startlft', $left, PDO::PARAM_INT);
$dbms->bindparam(':endlft', $right+1, PDO::PARAM_INT);
$dbms->query();
// decrement father2 and father3 (and descendants)
$dbms->prepare("UPDATE categories SET lft=lft-:deltalft, rgt=rgt-:deltargt WHERE lft>=:startlft AND lft<:endlft;");
$dbms->bindparam(':deltalft', $delta, PDO::PARAM_INT);
$dbms->bindparam(':deltargt', $delta, PDO::PARAM_INT);
$dbms->bindparam(':startlft', $right+1, PDO::PARAM_INT);
$dbms->bindparam(':endlft', $drop, PDO::PARAM_INT);
$dbms->query();
$dbms->commit();
这没有按预期工作。这是结果:
+---------+-----+-----+
| id | lft | rgt |
+---------+-----+-----+
| root | 1 | 20 |
| father2 | 2 | 3 |
| father3 | 4 | 11 |
| child3 | 5 | 6 |
| child4 | 7 | 8 |
| child5 | 9 | 10 |
| father1 | 6 | 11 | // should be 12 17
| child1 | 7 | 8 | // should be 13 14
| child2 | 9 | 10 | // should be 15 16
| father4 | 18 | 19 |
+---------+-----+-----+
我认为这可能与更新的并发性有关。第一次更新后,只有 father1
(和后代)被设置为最终值。第二次更新应该只影响 father2
和 father3
(以及后代),实际上也会影响 father1
。
有没有办法在 PDO 中处理级联更新?
您在两个查询的 WHERE 上定义了相同的间隔,这就是问题所在:
WHERE lft>=:startlft AND lft<:endlft;
所以发生的事情是,在第一个查询中,它将 gap
添加到 father 1 left
-> 2+10 = 12。父亲 1 left 现在是 12,如你所愿。
然后,在第二个查询中,不是从 father 2 left
和 father 3 left
中获取 delta
,而是从 father 1 left
-> 12-6 = 6 中获取 delta
。父亲 1 left 现在是 6。父亲 1 right 也是如此,7+10=17 然后 17-6 = 11。第二个查询不会更新父亲 2 和 3,因为它们不符合上面提供的间隔。
您必须更正 WHERE 在第二个查询中选择的间隔,以便它可以更新父亲 2 和 3 而不是更新父亲 1。将第二个查询更改为这个查询,这一定可以解决问题。
// decrement father2 and father3 (and descendants)
$dbms->prepare("UPDATE categories SET lft=lft-:deltalft, rgt=rgt-:deltargt WHERE lft<:startlft OR lft>=:endlft;");
经过一番思考,我意识到所需的操作相当于在一个循环区间内求和。
如果我们考虑区间 (lft,rgt) = [2,18)
,向所有节点(和后代)添加足够的量以将 father1
(和后代)定位在区间 (18)
的顶部,我们可以获得所需的结果。为此,我们需要
- 计算将
father1
(和后代)移动到顶部所需的数量。这等于 father1
和顶部之间的 "distance":18-8 = 10
- 将此金额添加到所有节点
- 将所有节点向下平移,让区间的底部与
0
重合。在这种情况下,我们需要从所有节点中减去 2
。下一步(重要)步骤需要此操作
- 在所有节点上进行
modulo
操作。 modulo
的除数必须等于区间的宽度(在我们的例子中是 18-2 = 16
)。这允许 father2
和 father3
从区间顶部退出并从底部重新进入并保持 father1
不变
- 将
2
的区间再次向上平移,重新建立原来的底部值
让我们看一下实现它的代码。
$left = 2; // father1 left value
$right = 7; // father1 right value
$drop = 18; // insert point value
$gap = $drop - $right - $left - 1; // distance between father1 and the top, minus the initial offset (equal to the bottom of interval)
$size = $drop - $left; // 16, size of the interval
$dbms->prepare("UPDATE categories SET lft=:offsetlft+MOD(lft+:gaplft,:sizelft), rgt=:offsetrgt+MOD(rgt+:gaprgt,:sizergt) WHERE lft>=:startlft AND lft<:endlft;");
$dbms->bindparam(':offsetlft', $left, PDO::PARAM_INT);
$dbms->bindparam(':offsetrgt', $left, PDO::PARAM_INT);
$dbms->bindparam(':gaplft', $gap, PDO::PARAM_INT);
$dbms->bindparam(':gaprgt', $gap, PDO::PARAM_INT);
$dbms->bindparam(':sizelft', $size, PDO::PARAM_INT);
$dbms->bindparam(':sizergt', $size, PDO::PARAM_INT);
$dbms->bindparam(':startlft', $left, PDO::PARAM_INT);
$dbms->bindparam(':endlft', $drop, PDO::PARAM_INT);
$dbms->query();
因为我们只有一个查询,所以我们可以删除 beginTransaction()
和 commit()
语句。结果如预期
+---------+-----+-----+
| id | lft | rgt |
+---------+-----+-----+
| root | 1 | 20 |
| father2 | 2 | 3 |
| father3 | 4 | 11 |
| child3 | 5 | 6 |
| child4 | 7 | 8 |
| child5 | 9 | 10 |
| father1 | 12 | 17 |
| child1 | 13 | 14 |
| child2 | 15 | 16 |
| father4 | 18 | 19 |
+---------+-----+-----+
简单快捷。希望对大家有帮助。
我有一个 MySql table 包含使用 modified preorder tree traversal 方法排序的节点。 每个节点都有一个id,一个左值和一个右值。
+---------+-----+-----+
| id | lft | rgt |
+---------+-----+-----+
| root | 1 | 20 |
| father1 | 2 | 7 |
| child1 | 3 | 4 |
| child2 | 5 | 6 |
| father2 | 8 | 9 |
| father3 | 10 | 17 |
| child3 | 11 | 12 |
| child4 | 13 | 14 |
| child5 | 15 | 16 |
| father4 | 18 | 19 |
+---------+-----+-----+
我的目标是在father3
之后带上father1
,得到这个结果。
+---------+-----+-----+
| id | lft | rgt |
+---------+-----+-----+
| root | 1 | 20 |
| father2 | 2 | 3 |
| father3 | 4 | 11 |
| child3 | 5 | 6 |
| child4 | 7 | 8 |
| child5 | 9 | 10 |
| father1 | 12 | 17 |
| child1 | 13 | 14 |
| child2 | 15 | 16 |
| father4 | 18 | 19 |
+---------+-----+-----+
要执行此操作,我需要:
- 增加
father1
(和后代)10
的左右值
- 左右递减
father2
和father3
(以及后代)6
的值
我正在尝试使用 PDO 执行此更新。
$left = 2; // father1 left value
$right = 7; // father1 right value
$drop = 18; // insert point value (will be after new father1 position)
$delta = $right - $left + 1; // 6
$gap = $drop - $right - 1; // 10
$dbms->beginTransaction();
// increment father1 (and descendants)
$dbms->prepare("UPDATE categories SET lft=lft+:gaplft, rgt=rgt+:gaprgt WHERE lft>=:startlft AND lft<:endlft;");
$dbms->bindparam(':gaplft', $gap, PDO::PARAM_INT);
$dbms->bindparam(':gaprgt', $gap, PDO::PARAM_INT);
$dbms->bindparam(':startlft', $left, PDO::PARAM_INT);
$dbms->bindparam(':endlft', $right+1, PDO::PARAM_INT);
$dbms->query();
// decrement father2 and father3 (and descendants)
$dbms->prepare("UPDATE categories SET lft=lft-:deltalft, rgt=rgt-:deltargt WHERE lft>=:startlft AND lft<:endlft;");
$dbms->bindparam(':deltalft', $delta, PDO::PARAM_INT);
$dbms->bindparam(':deltargt', $delta, PDO::PARAM_INT);
$dbms->bindparam(':startlft', $right+1, PDO::PARAM_INT);
$dbms->bindparam(':endlft', $drop, PDO::PARAM_INT);
$dbms->query();
$dbms->commit();
这没有按预期工作。这是结果:
+---------+-----+-----+
| id | lft | rgt |
+---------+-----+-----+
| root | 1 | 20 |
| father2 | 2 | 3 |
| father3 | 4 | 11 |
| child3 | 5 | 6 |
| child4 | 7 | 8 |
| child5 | 9 | 10 |
| father1 | 6 | 11 | // should be 12 17
| child1 | 7 | 8 | // should be 13 14
| child2 | 9 | 10 | // should be 15 16
| father4 | 18 | 19 |
+---------+-----+-----+
我认为这可能与更新的并发性有关。第一次更新后,只有 father1
(和后代)被设置为最终值。第二次更新应该只影响 father2
和 father3
(以及后代),实际上也会影响 father1
。
有没有办法在 PDO 中处理级联更新?
您在两个查询的 WHERE 上定义了相同的间隔,这就是问题所在:
WHERE lft>=:startlft AND lft<:endlft;
所以发生的事情是,在第一个查询中,它将 gap
添加到 father 1 left
-> 2+10 = 12。父亲 1 left 现在是 12,如你所愿。
然后,在第二个查询中,不是从 father 2 left
和 father 3 left
中获取 delta
,而是从 father 1 left
-> 12-6 = 6 中获取 delta
。父亲 1 left 现在是 6。父亲 1 right 也是如此,7+10=17 然后 17-6 = 11。第二个查询不会更新父亲 2 和 3,因为它们不符合上面提供的间隔。
您必须更正 WHERE 在第二个查询中选择的间隔,以便它可以更新父亲 2 和 3 而不是更新父亲 1。将第二个查询更改为这个查询,这一定可以解决问题。
// decrement father2 and father3 (and descendants)
$dbms->prepare("UPDATE categories SET lft=lft-:deltalft, rgt=rgt-:deltargt WHERE lft<:startlft OR lft>=:endlft;");
经过一番思考,我意识到所需的操作相当于在一个循环区间内求和。
如果我们考虑区间 (lft,rgt) = [2,18)
,向所有节点(和后代)添加足够的量以将 father1
(和后代)定位在区间 (18)
的顶部,我们可以获得所需的结果。为此,我们需要
- 计算将
father1
(和后代)移动到顶部所需的数量。这等于father1
和顶部之间的 "distance":18-8 = 10
- 将此金额添加到所有节点
- 将所有节点向下平移,让区间的底部与
0
重合。在这种情况下,我们需要从所有节点中减去2
。下一步(重要)步骤需要此操作 - 在所有节点上进行
modulo
操作。modulo
的除数必须等于区间的宽度(在我们的例子中是18-2 = 16
)。这允许father2
和father3
从区间顶部退出并从底部重新进入并保持father1
不变 - 将
2
的区间再次向上平移,重新建立原来的底部值
让我们看一下实现它的代码。
$left = 2; // father1 left value
$right = 7; // father1 right value
$drop = 18; // insert point value
$gap = $drop - $right - $left - 1; // distance between father1 and the top, minus the initial offset (equal to the bottom of interval)
$size = $drop - $left; // 16, size of the interval
$dbms->prepare("UPDATE categories SET lft=:offsetlft+MOD(lft+:gaplft,:sizelft), rgt=:offsetrgt+MOD(rgt+:gaprgt,:sizergt) WHERE lft>=:startlft AND lft<:endlft;");
$dbms->bindparam(':offsetlft', $left, PDO::PARAM_INT);
$dbms->bindparam(':offsetrgt', $left, PDO::PARAM_INT);
$dbms->bindparam(':gaplft', $gap, PDO::PARAM_INT);
$dbms->bindparam(':gaprgt', $gap, PDO::PARAM_INT);
$dbms->bindparam(':sizelft', $size, PDO::PARAM_INT);
$dbms->bindparam(':sizergt', $size, PDO::PARAM_INT);
$dbms->bindparam(':startlft', $left, PDO::PARAM_INT);
$dbms->bindparam(':endlft', $drop, PDO::PARAM_INT);
$dbms->query();
因为我们只有一个查询,所以我们可以删除 beginTransaction()
和 commit()
语句。结果如预期
+---------+-----+-----+
| id | lft | rgt |
+---------+-----+-----+
| root | 1 | 20 |
| father2 | 2 | 3 |
| father3 | 4 | 11 |
| child3 | 5 | 6 |
| child4 | 7 | 8 |
| child5 | 9 | 10 |
| father1 | 12 | 17 |
| child1 | 13 | 14 |
| child2 | 15 | 16 |
| father4 | 18 | 19 |
+---------+-----+-----+
简单快捷。希望对大家有帮助。