I see, that this topic is quite old, but anyway it's still unanswered. I got here from Google, and found no direct answer to this question.
So, after a little research I found quite easy solution.
Everything, what we gonna need to move our node is: node left and right positions, new parent node right position. The node to the new position then can be moved in four easy steps:
- Change positions of node ant all it's sub nodes into negative values,
which are equal to current ones by module.
- Move all positions "up", which are more, that pos_right of current node.
- Move all positions "down", which are more, that pos_right of new parent node.
- Change positions of current node and all it's subnodes, so that it's now will be exactly "after" (or "down") of new parent node.
That's theory, now - this algorithm realization in MySQL (example using PHP):
# step 0: Initialize parameters.
SELECT
@node_id := 1, #put there id of moving node
@node_pos_left := 0, #put there left position of moving node
@node_pos_right := 1, #put there right position of moving node
@parent_id := 2, #put there id of new parent node (there moving node should be moved)
@parent_pos_right := 4; #put there right position of new parent node (there moving node should be moved)
SELECT
@node_size := @node_pos_right - @node_pos_left + 1; # 'size' of moving node (including all it's sub nodes)
# step 1: temporary "remove" moving node
UPDATE `list_items`
SET `pos_left` = 0-(`pos_left`), `pos_right` = 0-(`pos_right`)
WHERE `pos_left` >= @node_pos_left AND `pos_right` <= @node_pos_right;
# step 2: decrease left and/or right position values of currently 'lower' items (and parents)
UPDATE `list_items`
SET `pos_left` = `pos_left` - @node_size
WHERE `pos_left` > @node_pos_right;
UPDATE `list_items`
SET `pos_right` = `pos_right` - @node_size
WHERE `pos_right` > @node_pos_right;
# step 3: increase left and/or right position values of future 'lower' items (and parents)
UPDATE `list_items`
SET `pos_left` = `pos_left` + @node_size
WHERE `pos_left` >= IF(@parent_pos_right > @node_pos_right, @parent_pos_right - @node_size, @parent_pos_right);
UPDATE `list_items`
SET `pos_right` = `pos_right` + @node_size
WHERE `pos_right` >= IF(@parent_pos_right > @node_pos_right, @parent_pos_right - @node_size, @parent_pos_right);
# step 4: move node (ant it's subnodes) and update it's parent item id
UPDATE `list_items`
SET
`pos_left` = 0-(`pos_left`)+IF(@parent_pos_right > @node_pos_right, @parent_node_right - @node_pos_right - 1, @parent_pos_right - @node_pos_right - 1 + @node_size),
`pos_right` = 0-(`pos_right`)+IF(@parent_pos_right > @node_pos_right, @parent_node_right - @node_pos_right - 1, @parent_pos_right - @node_pos_right - 1 + @node_size)
WHERE `pos_left` <= 0-@node_pos_left AND `pos_right` >= 0-@node_pos_right;
UPDATE `list_items`
SET `parent_item_id` = @parent_id
WHERE `item_id` = @node_id;
Please beware - there still may be some syntax errors in SQL code, because I actually use this algorithm in PHP like this:
$iItemId = 1;
$iItemPosLeft = 0;
$iItemPosRight = 1;
$iParentId = 2;
$iParentPosRight = 4;
$iSize = $iPosRight - $iPosLeft + 1;
$sql = array(
// step 1: temporary "remove" moving node
'UPDATE `list_items`
SET `pos_left` = 0-(`pos_left`), `pos_right` = 0-(`pos_right`)
WHERE `pos_left` >= "'.$iItemPosLeft.'" AND `pos_right` <= "'.$iItemPosRight.'"',
// step 2: decrease left and/or right position values of currently 'lower' items (and parents)
'UPDATE `list_items`
SET `pos_left` = `pos_left` - '.$iSize.'
WHERE `pos_left` > "'.$iItemPosRight.'"',
'UPDATE `list_items`
SET `pos_right` = `pos_right` - '.$iSize.'
WHERE `pos_right` > "'.$iItemPosRight.'"',
// step 3: increase left and/or right position values of future 'lower' items (and parents)
'UPDATE `list_items`
SET `pos_left` = `pos_left` + '.$iSize.'
WHERE `pos_left` >= "'.($iParentPosRight > $iItemPosRight ? $iParentPosRight - $iSize : $iParentPosRight).'"',
'UPDATE `list_items`
SET `pos_right` = `pos_right` + '.$iSize.'
WHERE `pos_right` >= "'.($iParentPosRight > $iItemPosRight ? $iParentPosRight - $iSize : $iParentPosRight).'"',
// step 4: move node (ant it's subnodes) and update it's parent item id
'UPDATE `list_items`
SET
`pos_left` = 0-(`pos_left`)+'.($iParentPosRight > $iItemPosRight ? $iParentPosRight - $iItemPosRight - 1 : $iParentPosRight - $iItemPosRight - 1 + $iSize).',
`pos_right` = 0-(`pos_right`)+'.($iParentPosRight > $iItemPosRight ? $iParentPosRight - $iItemPosRight - 1 : $iParentPosRight - $iItemPosRight - 1 + $iSize).'
WHERE `pos_left` <= "'.(0-$iItemPosLeft).'" AND i.`pos_right` >= "'.(0-$iItemPosRight).'"',
'UPDATE `list_items`
SET `parent_item_id` = "'.$iParentItemId.'"
WHERE `item_id`="'.$iItemId.'"'
);
foreach($sql as $sqlQuery){
mysql_query($sqlQuery);
}
Please note also, that code may be optimized, but I going to leave it like that for better readability. Also consider table locking if you are using nested sets in multi-user systems.
Hope that my message will help to anyone, who will search for a solution after me. Any comments and corrections are also welcome.