在嵌套集树中移动节点

时间:2010-05-10 08:28:37

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

我正在使用mySQL创建一个邻接列表,并且不能(至少我自己)做出必要的思考,以便能够移动一组节点(连同最终的子节点)。

该表包含以下列:

 id     name     left     right

非常感谢!

4 个答案:

答案 0 :(得分:9)

这是一个解决方案,它允许您将节点移动到树中的任何位置,只需一个输入参数 - 节点的新左侧位置(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;
}

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

http://www.ninthavenue.com.au/how-to-move-a-node-in-nested-sets-with-sql

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

答案 1 :(得分:4)

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

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

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


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

答案 2 :(得分:4)

使用嵌套集模型(左右列)在类别树中移动子树的步骤如下: 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));

答案 3 :(得分:3)

我有一个更简单,更容易阅读的SQL,它对我来说非常适合。它是一个典型的嵌套集结构,具有id,rgt,lft,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...