我正在使用 mySQL 处理邻接列表,并且不能(至少我自己)做一个足够体面的查询所需的思考,以便能够移动一组节点(连同最终的子节点)。
该表有以下列:
id name left right
非常感谢!
我正在使用 mySQL 处理邻接列表,并且不能(至少我自己)做一个足够体面的查询所需的思考,以便能够移动一组节点(连同最终的子节点)。
该表有以下列:
id name left right
非常感谢!
这是一个解决方案,可让您将节点移动到树中的任何位置,只需一个输入参数 - 节点的新左侧位置 (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
如果您喜欢此解决方案,请投票。
我很确定该表使用的是嵌套集设计,而不是邻接列表。如果它使用邻接列表,它将有一个类似于parent_id
而不是left
and的列right
。
移动节点是嵌套集中的皇家 PITA。您必须为您移动的每个节点重新编号所有left
和right
值。
如果移动子树,最简单的方法是一次删除一个节点,在每次删除节点后重新编号left
和right
字段。然后,一旦您删除了整个子树(并以某种方式在应用程序中保留了子树的结构),在树中的目标位置重新插入子树,再次对每次插入的left
和right
字段重新编号。
更新:我最近写了一篇关于如何在不同的分层数据设计中移动子树的博客,我比嵌套集更喜欢这种设计。我称这种设计为 Closure Table。
见http://www.mysqlperformanceblog.com/2011/02/14/moving-subtrees-in-closure-table/
使用嵌套集模型(具有左右列)在类别树中移动子树的步骤是: 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));
我有一个更简单、更容易阅读的 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...