PDO中的级联数据更新

时间:2015-01-29 16:12:07

标签: php mysql pdo

我有一个MySql表,其中包含使用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  |
+---------+-----+-----+

我的目标是在father1之后提出father3,并获得此结果。

+---------+-----+-----+
| 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
  • 的左右值
  • 左右递减father2father3(以及后代) 值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(和后代)设置为最终值。第二次更新仅影响father2father3(以及后代),实际上也会影响father1

有没有办法在PDO中处理级联更新?

2 个答案:

答案 0 :(得分:0)

您在两个查询的WHERE上定义了相同的间隔,这就是问题所在:

WHERE lft>=:startlft AND lft<:endlft;

所以正在发生的事情是,在第一个查询中,它将gap添加到father 1 left - &gt; 2 + 10 = 12.父亲1离开现在是12,如你所愿。 然后,在第二次查询时,不是从deltafather 2 left获取father 3 left,而是从delta获取father 1 left - &gt; 12-6 = 6.父亲1左边现在是6.同样的情况发生在父亲1右边,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;");

答案 1 :(得分:0)

经过一番思考后,我意识到所需的操作相当于在一个循环间隔内进行求和。

如果我们考虑时间间隔(lft,rgt) = [2,18),请向所有节点(和后代)添加足够的金额以将father1(和后代)置于区间(18)的顶部,我们可以获得所需的结果。为此,我们需要

  1. 计算将father1(和后代)移至顶部所需的金额。这相当于&#34;距离&#34;在father1和顶部之间:18-8 = 10
  2. 将此金额添加到所有节点
  3. 将所有节点向下翻译,让间隔的底部与0重合。在这种情况下,我们需要从所有节点中减去2。下一步(重要)步骤
  4. 需要此操作
  5. 在所有节点上进行modulo操作。 modulo必须具有等于间隔宽度的除数(在我们的例子中为18-2 = 16)。这样,father2father3可以从区间顶部退出并从底部重新输入并保持father1不变
  6. 再次将2的时间间隔翻译为重新建立原始最低值
  7. 让我们看看实现它的代码。

    $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  |
    +---------+-----+-----+
    

    简单快捷。希望它可以帮到某人。