Move node in nested set

后端 未结 13 988
孤街浪徒
孤街浪徒 2020-12-07 15:06

I\'d need a MySQL query that moves a node and all its children within a nested set. I found this site, but that function just seems so illogical - there\'s no universe

相关标签:
13条回答
  • 2020-12-07 15:40

    $row is an array that represents the row I have to move; it must be like this:

    Array ( [lft] => 5 [rgt] => 10 [width] => 6 ) 
    

    $row2 is an array that represents the destiny node;

    Array ( [id] => 5 [lft] => 2 [rgt] => 17 [width] => 16 ) 
    

    ...

    mysql_query("UPDATE entryCategory SET rgt = rgt + %d - %d, lft = lft + %d - %d WHERE rgt <= %d and lft >= %d;",$row2["rgt"],$row["lft"],$row2["rgt"],$row["lft"],$row["rgt"],$row["lft"]);
    mysql_query("UPDATE entryCategory SET rgt = rgt + %d WHERE id=%d;",$row["width"],$row2["id"]);
    mysql_query("UPDATE entryCategory SET rgt = rgt - %d, lft = lft - %d  WHERE rgt > %d and lft > %d;",$row["width"],$row["width"],$row["rgt"],$row["rgt"]);
    
    0 讨论(0)
  • 2020-12-07 15:44

    Thanks for the idea of transforming the lft and rgt to their negative counterparts. I posted a more general approach for this here: Move node in Nested Sets tree.

    The queryBatch() function encloses the query in a transaction.

    0 讨论(0)
  • 2020-12-07 15:44

    It is quite simple, first define a stored procedure:

       CREATE DEFINER=`root`@`localhost` PROCEDURE `move_item`(
            IN itemId BIGINT, IN kind SMALLINT, 
            IN newSiblingId BIGINT UNSIGNED, IN newSiblingKind SMALLINT, IN newParentId BIGINT UNSIGNED,
            IN jobId BIGINT UNSIGNED, IN companyId BIGINT UNSIGNED,
            OUT outSucess SMALLINT UNSIGNED)
    proc_label:BEGIN
    

    next we need some local variables:

    DECLARE oldLeft, oldRight, newLeft, newRight, itemWidth, moveBy INT UNSIGNED DEFAULT 0; 
    set outSucess =0;
    

    Now get our old left & right and get the width

    SELECT `LFT`, `RGT` into oldLeft, oldRight from `nodes` where `ID`=itemId LIMIT 1;
    SET itemWidth = oldRight - oldLeft + 1;
    

    Now take them "out of the tree" by multiplying by -1

    UPDATE `nodes` SET `RGT`=`RGT`* -1, `LFT`=`LFT`* -1 WHERE ``LFT` BETWEEN oldLeft and oldRight;
    

    The next part is not necessary as the tree will work without it, but it is neat; close the old gap:

    -- Update right
    UPDATE `nodes` SET `RGT` = `RGT` - itemWidth WHERE `RGT` > oldRight;
    -- Update left
    UPDATE `nodes` SET `LFT` = `LFT` - itemWidth WHERE `LFT` > oldRight;
    

    Now find the new location:

    SELECT (`RGT`+1) into newLeft from `nodes`  where `ID`=newSiblingId LIMIT 1;
    
    -- No sibling, so make it last in parent
    IF (newLeft = 0) AND (newParentId != 0) THEN
      SELECT `RGT` into newLeft from `nodes` WHERE `ID`=newParentId LIMIT 1;
    END IF;
    
     -- If no previous sibling or parent, set to first item in tree
    IF (newLeft=0) OR (newLeft=NULL) THEN SET newLeft=1;
    END IF;
    

    Now make some space:

     -- Update right
    UPDATE `nodes` SET `RGT` = `RGT` + itemWidth WHERE `RGT` >= newLeft;
    -- Update left
    UPDATE `nodes` SET `LFT` = `LFT` + itemWidth WHERE `LFT` >= newLeft;
    

    Finally move the nodes that where shifted out of the tree back in by * -1, and while you are at it, move them to the correct location as well:

    SET moveBy = OldLeft - NewLeft;
    UPDATE `nodes` SET `RGT`=(`RGT`* -1)-moveBy, `LFT`=(`LFT`* -1)-moveBy WHERE `LFT` < 0;
    set outSucess =1;
    

    Not tested, pasted and adjusted and simplified from a working routine.

    0 讨论(0)
  • 2020-12-07 15:47
    # Get Left and Right offsets of both source node (Drag Node) and target node (Drop off Node).
    SELECT lft,rgt INTO @sourceNodeLft,@sourceNodeRgt FROM treetest WHERE id=_sourceNodeId;
    SELECT lft,rgt INTO @targetNodeLft,@targetNodeRgt FROM treetest WHERE id=_targetNodeId;
    
    # Determine node order direction
    SET @direction := IF(@targetNodeLft<@sourceNodeLft,'UP','DOWN'); 
    
    # Determine with of source node (Drag Node)
    SET @width := @sourceNodeRgt - @sourceNodeLft + 1;
    
    # Mark all displaced nodes with negative lft and rgt
    UPDATE treetest SET lft = 0-lft, rgt = 0-rgt 
                                    WHERE lft >= @targetNodeLft AND rgt <= @targetNodeRgt;
    UPDATE treetest SET lft = 0-lft, rgt = 0-rgt 
                                    WHERE lft >= @sourceNodeLft AND rgt <= @sourceNodeRgt;
    
    
    # Update left and right offsets of inner nodes between source (Drag Node)and target (Drop off) node
    UPDATE treetest SET lft =   (lft + IF(@direction = 'UP',@width,0-@width)),
                                        rgt = (rgt + IF(@direction = 'UP',@width,0-@width))
                                    WHERE lft > IF(@direction = 'UP', @targetNodeLft, @sourceNodeLft)
                                                AND rgt < IF(@direction = 'UP', @sourceNodeLft, @targetNodeLft);
    
    
    # Update source (Drag) Node and its children offsets  
    SET @sourceOffset := IF(@direction = 'UP',@targetNodeLft - @sourceNodeLft, @targetNodeRgt - @width - @sourceNodeLft+1);
    UPDATE treetest SET lft = 0 - lft + @sourceOffset, 
                                            rgt = 0 - rgt + @sourceOffset
                                    WHERE (0-lft) >= @sourceNodeLft AND (0-rgt) <= @sourceNodeRgt;
    
    # Update target (Drop off) node and its children offsets
    SET @targetOffset := IF(@direction = 'UP', 0 - @width,@width);
    UPDATE treetest SET lft = 0 - (lft + @targetOffset),
                                            rgt = 0 - (rgt + @targetOffset)
                                    WHERE (0-lft) >= @targetNodeLft AND (0-rgt) <= @targetNodeRgt;
    
    
    
    0 讨论(0)
  • 2020-12-07 15:51

    I believe that with two extra columns to store the original Node right and left values (and all subsequent subnodes) the algorithm can be simplified. I have worked the examples with pencil and paper so if you find any holes in the algorithm please let me know.

    The target Node (The new parent of Node that you are moving) is tNode. Left value of Target Node is tNode.L and right value is tNode.R. Similarly the node you are moving is mNode and left and right values for mNode are mNode.L and mNode.R. The two extra columns are mNode.SL and mNode.SR

    So all in all we have 4 columns for manipulation R,L, SL and SR


    Step1

    calculate

    delta1 = (mNode.R - mNode.L) + 1 
    

    Step2

    Save the mNode original L and R into SL and SR columns

    - For All L between mNode.L and mNode.R 
       mNode.SL = mNode.L ; mNode.L = 0 ;
     - For All R between mNode.L and mNode.R 
       mNode.SR = mNode.R ; mNode.R = 0 ;
    

    Step3

    Do For all Nodes
    IF L > mNode.SR 
       L = L + delta1
    IF R > mNode.SR
       R = R + delta1
    

    Now the mNode is detached from Tree and Tree is adjusted without mNode.

    Step4

    calculate

    delta2 = (tNode.R - mNode.SL)
    

    Step5

    Do for all Nodes
      IF L >= tNode.R
        L = L + delta1
      IF R >= tNode.R
        R = R + delta1
    

    Now we have adjusted the Tree (and target node) to accept the number of nodes that were deleted.

    Step6

    Attach mNode at tNode and reset SL/SR column values

    Do for all Nodes
     IF SL between mNode.SL and mNode.SR
        L = mNode.SL + delta2 ; mNode.SL = 0  ;
     IF SR between mNode.SL and mNode.SR
        R = mNode.SR + delta2 ; mNode.SR = 0 ;
    

    After all these steps we should have moved mNode under the tNode.

    0 讨论(0)
  • 2020-12-07 15:53

    I have a stored procedure that moves a node in a nested set to a new parent node. I am using a table called "category" in a MySQL / InnoDB database called "somedb". Of course, if the destination is a subcategory of the category you want to move this procedure will screw things up, so make sure that you aren't trying to embed a node inside of itself. I will leave it as an exercise to the reader to make this procedure safe for that case.

    CREATE PROCEDURE `somedb`.`moveCatParent` (IN cat_a VARCHAR(45), IN cat_b VARCHAR(45))
    BEGIN
        START TRANSACTION;
    
        /* cat_b.lft + 1 is the destination. */
        SELECT @destination := (lft + 1)
        FROM category
        WHERE name = cat_b;
    
        SELECT @cat_a_width := ((rgt - lft) + 1)
        FROM category
        WHERE name = cat_a;
    
        /* Rip this table a new cat_a sized hole inside cat_b. */  
        UPDATE category SET rgt = rgt + @cat_a_width WHERE rgt >= @destination;
        UPDATE category SET lft = lft + @cat_a_width WHERE lft >= @destination;
    
        SELECT @cat_a_lft := lft, @cat_a_rgt := rgt
        FROM category
        WHERE name = cat_a;
    
        SELECT @diff := @destination - @cat_a_lft;
    
        /* Move cat_a and all inhabitants to new hole */  
        UPDATE category SET rgt = rgt + @diff WHERE rgt BETWEEN @cat_a_lft AND @cat_a_rgt;
        UPDATE category SET lft = lft + @diff WHERE lft BETWEEN @cat_a_lft AND @cat_a_rgt;
    
        /* Close the gap created when we moved cat_a. */
        UPDATE category SET rgt = rgt - @cat_a_width WHERE rgt >= @cat_a_lft;
        UPDATE category SET lft = lft - @cat_a_width WHERE lft >= @cat_a_lft;
    
        COMMIT;
    END
    
    0 讨论(0)
提交回复
热议问题