【问题标题】:PHP/MySQL - reorder a numerical sequence when changing a valuePHP/MySQL - 更改值时重新排序数字序列
【发布时间】:2018-01-05 15:14:27
【问题描述】:

我正在开发一个将树存储在数据库中的应用程序。该应用程序是使用 CakePHP 2.x 和 jstree 3.2.1 构建的

保存数据的架构如下:

  • id - 自动递增 ID,对于树的每个节点都是唯一的
  • parent_id - 元素的父 ID; null 如果没有父级(顶级元素)。
  • lft, rght - 使用 CakePHP 的 Tree Behaviour,它使用 Modified Preorder Tree Traversal。这些代表 lftrght 值。
  • name - 节点的文本名称
  • pos - 一个从 0 开始的整数,用于确定节点在其父节点下的位置。

我遇到的问题是在对树中节点的位置进行更新时“调整”所有 pos 元素。

例如,考虑以下数据:

id    |    parent_id    |    name    |     pos
--------------------------------------------------
149   |    NULL         |    Foo     |     0
150   |    149          |    A       |     0
151   |    149          |    B       |     1
152   |    149          |    C       |     2
153   |    149          |    D       |     3
154   |    149          |    E       |     4
155   |    149          |    F       |     5

在视觉上,如下所示:

    • 一个
    • B
    • C
    • D
    • E
    • F

现在,如果我将“E”拖放到“B”和“C”之间...

    • 一个
    • B
    • E(移动
    • C
    • D
    • F

...我需要更新我的数据库中的 pos 值,用于“Foo”的所有子项。

当拖放发生时,jstree 为我提供:

  1. 我移动的元素的id。 “E”是154
  2. parent_id,“E”已被删除。在这种情况下,它是 149,因为它仍然是“Foo”的子元素,但理论上可以移动到树上的任何其他元素下。
  3. 它被放入的pos(位置)。它们从0开始,所以在上面的例子中,移动“E”后它位于pos = 2

了解节点可以向上或向下拖动是很重要的,但 jstree 只会提供与其在父节点下的位置相关的pos(以及parent_idid)。

所以我可以用“E”的新pos 更新我的数据库:

>UPDATE tree SET pos = 2 WHERE id = 154 AND parent_id = 149;

id    |    parent_id    |    name    |     pos
--------------------------------------------------
149   |    NULL         |    Foo     |     0
150   |    149          |    A       |     0
151   |    149          |    B       |     1
152   |    149          |    C       |     2
153   |    149          |    D       |     3
154   |    149          |    E       |     2 (*updated*)
155   |    149          |    F       |     5

我遇到的问题是如何重新排列其他 pos 值。例如,“C”现在应该有一个pos4

GitHub上有一些代码显示an example of how the data is saved when a node is moved,但我无法遵循(尽管经历了几天)或将其调整为CakePHP。事实上,提供的大部分代码都是不必要的,因为 Cake 负责生成 lftrght 值。所以我想知道的是如何使用 PHP/MySQL 将数字“转换”或“调整”回数字序列

我提供这个以防万一:

pos 值按顺序在每个父节点 (parent_id) 下。因此,如果我们有另一个父元素“Bar”,它有子元素“XX”、“YY”和“ZZ”,数据可能看起来像这样。注意pos 的值是什么。

所以查询应该只在任何特定时间为给定的parent_id 调整pos 的序列。在前面的示例中,我只想将 pos 重新排序为 parent_id = 149 - 假设我正在移动节点“E”。

id    |    parent_id    |    name    |     pos
--------------------------------------------------
149   |    NULL         |    Foo     |     0
150   |    149          |    A       |     0
151   |    149          |    B       |     1
152   |    149          |    C       |     2
153   |    149          |    D       |     3
154   |    149          |    E       |     4
155   |    149          |    F       |     5
906   |    NULL         |    Bar     |     0
907   |    906          |    XX      |     0
908   |    906          |    YY      |     1
909   |    906          |    ZZ      |     2

【问题讨论】:

  • 您应该能够使用 Tree 行为的 moveUp 和 moveDown 方法吗? book.cakephp.org/2.0/en/core-libraries/behaviors/…
  • 我也这么认为,但事实并非如此。我们只有pos 值可以使用,所以我们不知道移动它的方向,甚至不知道它相对于它移动之前的位置的数量。我也花了很长时间研究它,但在这种情况下它无法使用。
  • 您是否尝试过使用我在上一个问题中描述的方法?持有该职位的列似乎是多余的,因为这就是 lft 列的用途。
  • @ndm 否,因为您在哪里说“确定moveUp/Down() 的增量”,根本不清楚如何做到这一点。我提供给 GitHub 的链接显示开发人员同时使用了pos 字段以及lftrght,所以我认为这是有原因的。此外,您不能使用 Cake 提供的 moveUp()moveDown(),因为您不知道节点已移入哪个方向(向上或向下)。您只知道它在父节点下方的相对位置 - 例如如果pos 值为 2,则表示它在列表中的第三个(第三个不是第二个,因为索引为 0)。
  • 您可以通过从新位置减去当前位置(在子节点列表中)并检查结果是负数(上移)还是正数(下移)来确定方向。我不知道开发人员为什么选择做他所做的事情,但是为了能够重新排序嵌套集,没有必要这样做,因为它实际上只是复制了已经由左值确定的内容。

标签: php mysql cakephp tree


【解决方案1】:

你有足够的信息来修改兄弟节点的位置。

如果 154 已移动到位置 2:

UPDATE tree SET pos = 2 WHERE id = 154 AND parent_id = 149;

我们想“删除”节点,然后再次“插入”回去。大致如下:

-- remove node from current position by updating nodes to the right to fill the gap
UPDATE tree SET pos = pos - 1 
       WHERE parent_id = 149 
       AND pos > (SELECT pos FROM tree WHERE id = 154);

-- insert node in new position by making space for it
UPDATE tree SET pos = pos + 1 WHERE parent_id = 149 AND pos >= 2;
UPDATE tree SET pos = 2 WHERE id = 154;

一般来说,当你在父母之间移动节点时:

:node_id       - id of node being moved
:new_parent_id - id of the new parent
:new_position  - new position


-- remove node from current position and update siblings to the right
UPDATE tree AS t
      JOIN tree AS n ON n.id = :node_id
      SET t.pos = t.pos - 1
      WHERE t.parent_id = n.parent_id AND t.pos > n.pos;

-- make space for new node under new parent
UPDATE tree
  SET pos = pos + 1
  WHERE parent_id = :new_parent_id
  AND pos >= :new_position;

-- update new position and new parent
UPDATE tree
  SET pos = :new_position, parent_id = :new_parent_id
  WHERE id = :node_id;

【讨论】:

  • 非常感谢您的帮助。问题是pos + 1 在您将节点向上移动时起作用。但是如果你要在“A”和“B”之间移动“E”呢?我们没有任何关于移动方向的数据。我们只会知道“E”现在在位置 1。如果我们应用您的逻辑,我们会得到A = 0, E = 1, B = 2, C = 3, D = 4, F = 6。 5 在哪里 - 它不见了?也许我们需要在移动(5)之前获得“E”的位置,然后在更高的元素上执行pos-1?我现在迷失了,所以无法清楚地思考,但你也许能看到我看不到的东西?
  • 好的,我第一次回答不完整,谢谢指出问题。不过很容易修复,您可以首先通过更新节点右侧的节点位置来“删除”节点(如果您将其描绘为列表),然后通过更新将节点“插入”到新位置它的位置,然后增加它的新位置右侧的所有内容。旧的 parent_id 和新的 parent_id 当然可以不同。
【解决方案2】:

Weltschmerz 的回答似乎可以正常工作。

但是,我会使用存储过程,将它们全部重新编号,然后在将项目“放置”到新位置后调用它。比如:

DELIMITER $$

DROP PROCEDURE IF EXISTS `renumberPos`$$
CREATE PROCEDURE `renumberPos`(
  in itemID int,
  in parentID int,
  in mPos int
)
declare cnt int default 0;
declare n int default 0;
declare pk int;
declare cur CURSOR for select id from tree 
                       where parent_id = parentID 
                       and id != itemID
                       order by pos;
BEGIN
  select count(*) into n from tree where parent_id = parentID

  open cur;

  WHILE cnt <= n+1 DO
    IF cnt = mPos then
      FETCH cur into pk;
      update tree set 
      pos = cnt 
      where id = pk;
      cnt = cnt + 1;
    END IF;

    FETCH cur into pk;
    update tree set 
    pos = cnt 
    where id = pk;
    cnt = cnt + 1;
  END WHILE;

  close cur;
END$$

DELIMITER ;

那么你可以称它为...

$sql = "CALL renumberPos($movedItemId, $itemParentID, $posMovedTo)";
if(mysqli_query($link, $sql)){
  echo "Procedure sorted the positions...";
}else{
  echo "ERROR: Couldn't execute $sql. " . mysqli_error($link);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多