【问题标题】:Move node in Nested Sets tree在嵌套集树中移动节点
【发布时间】:2010-05-10 08:28:36
【问题描述】:

我正在使用 mySQL 处理邻接列表,并且无法(至少我自己)进行所需的思考以做出足够体面的查询来移动一组节点(以及最终的子节点)。

表格有以下列:

 id     name     left     right

非常感谢!

【问题讨论】:

    标签: mysql tree nested hierarchical-data nested-sets


    【解决方案1】:

    这里有一个解决方案,可以让您将节点移动到树中的任何位置,只需一个输入参数 - 节点的新左侧位置 (newpos)。

    基本上有三套:

    • 为子树创建新空间。
    • 将子树移动到该空间中。
    • 删除子树腾出的旧空间。

    在psuedo-sql中是这样的:

    //
     *  -- create new space for subtree
     *  UPDATE tags SET lpos = lpos + :width WHERE lpos >= :newpos
     *  UPDATE tags SET rpos = rpos + :width WHERE rpos >= :newpos
     * 
     *  -- move subtree into new space
     *  UPDATE tags SET lpos = lpos + :distance, rpos = rpos + :distance
     *           WHERE lpos >= :tmppos AND rpos < :tmppos + :width
     * 
     *  -- remove old space vacated by subtree
     *  UPDATE tags SET lpos = lpos - :width WHERE lpos > :oldrpos
     *  UPDATE tags SET rpos = rpos - :width WHERE rpos > :oldrpos
     */
    

    :distance 变量是新旧位置之间的距离,:width 是子树的大小,:tmppos 用于跟踪在更新期间移动的子树。这些变量定义为:

    // calculate position adjustment variables
    int width = node.getRpos() - node.getLpos() + 1;
    int distance = newpos - node.getLpos();
    int tmppos = node.getLpos();
            
    // backwards movement must account for new space
    if (distance < 0) {
        distance -= width;
        tmppos += width;
    }
    

    有关完整的代码示例,请参阅我的博客

    https://rogerkeays.com/how-to-move-a-node-in-nested-sets-with-sql

    如果你喜欢这个解决方案,请投票。

    【讨论】:

    • 你的例子确实帮助我理解了算法,但是每次你把所有的方法体都放到一个 try 块中,你知道上帝会杀死一只小猫 :)
    【解决方案2】:

    我很确定该表使用的是 嵌套集 设计,而不是邻接列表。如果它使用邻接列表,它将有一个类似parent_id 的列,而不是leftright

    移动节点是嵌套集中的皇家 PITA。您必须为您移动的每个节点重新编号所有 leftright 值。

    如果您移动子树,最简单的方法是一次删除一个节点,在每次删除节点后重新编号 leftright 字段。然后,一旦您删除了整个子树(并以某种方式在应用程序中保留了子树的结构),在树中的目标位置重新插入子树,再次为每个插入重新编号 leftright 字段.


    更新:我最近写了一篇关于如何在不同的分层数据设计中移动子树的博客,我更喜欢这种设计而不是嵌套集。我将这种设计称为 Closure Table
    http://www.mysqlperformanceblog.com/2011/02/14/moving-subtrees-in-closure-table/

    【讨论】:

      【解决方案3】:

      使用嵌套集模型(左右列)在类别树中移动子树的步骤如下: 1. 将 lft 和 rgt 列转换为您希望移动的类别及其子类别的负数列(这将暂时从树中“删除”子树) 2. 如果您向上移动子树(或嵌套集表示中的“左”),则将子树的新父级与其旧左(或右,在第二种情况下)限制之间的所有类别向右移动,否则(当向下移动子树时)向右。这涉及将这些类别的左右列设置为它们的值加上(或减去,在第二种情况下)子树(或要移动的类别)左右列之间的距离 3.在此之后,您所要做的就是将左右列恢复为正值,同时减去(或在第二种情况下添加)其左限制与新父左列之间的差异(或在第二种情况下父级左右限制之间)

      这一切看起来很复杂,用一种情况来表达,所以我把它分解为两种情况:

      $step = 1+ $this->_categoriesTable->rgt
                      - $this->_categoriesTable->lft;
      $lft = $this->_categoriesTable->lft;
      $rgt = $this->_categoriesTable->rgt;
      $id = $this->_categoriesTable->id;
      $distance = $lft - $parentLeft - 1;
                      $query = '
                          UPDATE %s SET lft=-lft, rgt=-rgt
                              WHERE lft>=%d AND lft<=%d;
                          UPDATE %s SET lft=lft+%d WHERE lft>%d AND lft<%d;
                          UPDATE %s SET rgt=rgt+%d WHERE rgt>%d AND rgt<%d;
                          UPDATE %s SET lft=-lft-%d, rgt=-rgt-%d WHERE lft<=-%d
                              AND lft>=-%d;
                          UPDATE %s SET parent_id=%d, title=%s, description=%s,
                              metadescription=%s WHERE id=%s';
      
                      $query = sprintf($query,
                          $this->_db->nameQuote('#__categories'),
                              $lft, $rgt,
                          $this->_db->nameQuote('#__categories'), $step,
                              $parentLeft, $lft,
                          $this->_db->nameQuote('#__categories'), $step,
                              $parentLeft, $lft,
                          $this->_db->nameQuote('#__categories'), $distance,
                              $distance, $lft, $rgt,
                          $this->_db->nameQuote('#__categories'),
                              $data['parent_id'], 
                              $this->_db->Quote($this->_categoriesTable->title),
                              $this->_db->Quote($this->_categoriesTable->description),
                              $this->_db->Quote(
                                  $this->_categoriesTable->metadescription),
                              $this->_db->Quote($id));
      
      // and for the moving to the "right" case
      $step = 1+ $this->_categoriesTable->rgt
                      - $this->_categoriesTable->lft;
      $distance = $parentLeft - $this->_categoriesTable->rgt;
      
      // Memorize this because we bind and we need the old values
      $lft = $this->_categoriesTable->lft;
      $rgt = $this->_categoriesTable->rgt;
      $id = $this->_categoriesTable->id;
      
      $query = sprintf($query,
                          $this->_db->nameQuote('#__categories'),
                              $lft, $rgt,
                          $this->_db->nameQuote('#__categories'), $step,
                              $rgt, $parentLeft,
                          $this->_db->nameQuote('#__categories'), $step,
                              $rgt, $parentLeft,
                          $this->_db->nameQuote('#__categories'), $distance,
                              $distance, $lft, $rgt,
                          $this->_db->nameQuote('#__categories'),
                              $data['parent_id'],
                              $this->_db->Quote($this->_categoriesTable->title),
                              $this->_db->Quote($this->_categoriesTable->description),
                              $this->_db->Quote(
                                  $this->_categoriesTable->metadescription),
                              $this->_db->Quote($id));
      

      【讨论】:

        【解决方案4】:

        我有一个更简单、更容易阅读的 sql,它对我来说非常有用。它构成了一个典型的嵌套集合结构,带有 id、rgt、lft、level(也可以在没有 level 的情况下使用):

        #Set IDs
        SET @dirId := :dirId; #folder (subtree) you wanna move
        SET @targetId := :folderId; #target
        
        #get datas
        SELECT rgt, lft, rgt-lft+1, level INTO @dir_rgt, @dir_lft, @dir_size, @dir_level FROM files WHERE id = @dirId;
        
        #put the moving tree aside (lft and rgt columns must allow negative int)
        UPDATE files SET lft = 0-lft, rgt = 0-rgt WHERE lft BETWEEN @dir_lft AND @dir_rgt;
        
        #fill the empty space        
        UPDATE files SET rgt = rgt-@dir_size WHERE rgt > @dir_rgt;
        UPDATE files SET lft = lft-@dir_size WHERE lft > @dir_rgt;
        
        #get datas of the target-folder      
        SELECT lft, level INTO @target_lft, @target_level FROM files WHERE id = @targetId;
        
        #create space in the target-folder        
        UPDATE files SET rgt = rgt+@dir_size WHERE rgt >= @target_lft;
        UPDATE files SET lft = lft+@dir_size WHERE lft > @target_lft;
        
        #edit all nodes in the moving-tree
        UPDATE files SET
           lft     = 0 - lft - (@dir_lft - @target_lft - 1), #this formula fits for all moving directions
           rgt     = 0 - rgt - (@dir_lft - @target_lft - 1), 
           level   = level - (@dir_level - @target_level) + 1
        
        WHERE 
           lft < 0; #that could be more precise...
        

        【讨论】:

        • 非常感谢。经过 6 个小时的搜索和测试这么多解决方案,终于找到了适合我需要的解决方案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-10-29
        • 2013-10-07
        • 1970-01-01
        • 2022-09-28
        • 1970-01-01
        • 2021-09-05
        • 2020-08-10
        相关资源
        最近更新 更多