ネストされたセット内でノードとそのすべての子を移動する MySQL クエリが必要です。私はこのサイトを見つけましたが、その関数は非常に非論理的なようです-ネストされたセットモデルが存在しないuniverseid
か、存在せずtreeid
、コード自体が必要と思われるものよりも長くなります. 私がテーブルに持っている唯一の余分な列はparent
.
ID が失われるため、ノードを削除して再度追加することはできませんでした。
ネストされたセット内でノードとそのすべての子を移動する MySQL クエリが必要です。私はこのサイトを見つけましたが、その関数は非常に非論理的なようです-ネストされたセットモデルが存在しないuniverseid
か、存在せずtreeid
、コード自体が必要と思われるものよりも長くなります. 私がテーブルに持っている唯一の余分な列はparent
.
ID が失われるため、ノードを削除して再度追加することはできませんでした。
このトピックはかなり古いものですが、とにかくまだ答えがありません。私はGoogleからここに来ましたが、この質問に対する直接の答えは見つかりませんでした。
それで、少し調べた後、私は非常に簡単な解決策を見つけました。
すべて、ノードを移動するために必要なのは、ノードの左右の位置、新しい親ノードの右の位置です。ノードを新しい位置に移動するには、次の4つの簡単な手順を実行します。
これが理論です。MySQLでのこのアルゴリズムの実現(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_pos_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_pos_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;
注意してください-私は実際に次のようにPHPでこのアルゴリズムを使用しているため、SQLコードにはまだいくつかの構文エラーがある可能性があります。
$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);
}
また、コードは最適化されている可能性がありますが、読みやすくするためにそのままにしておきます。マルチユーザーシステムでネストされたセットを使用している場合は、テーブルのロックも検討してください。
私のメッセージが、私の後に解決策を探す人に役立つことを願っています。コメントや訂正も大歓迎です。
これは、単一の入力パラメーター (ノードの新しい左位置 (newlpos)) を持つ兄弟または子として、ノードをツリー内の任意の位置に移動できるソリューションです。
基本的には次の 3 つのステップがあります。
疑似 SQL では、次のようになります。
//
* -- create new space for subtree
* UPDATE tags SET lpos = lpos + :width WHERE lpos >= :newlpos
* UPDATE tags SET rpos = rpos + :width WHERE rpos >= :newlpos
*
* -- 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 = newlpos - 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
このソリューションが気に入ったら、賛成票を投じてください。
これは古い質問であることは知っていますが、私は自分自身で答えを使用しましたが、SQL Server 用です。誰かがそれを望むなら、受け入れられた答えに基づいた SQL Server Stored Proc のコードを次に示します。
CREATE PROCEDURE [dbo].[Item_Move]
@id uniqueidentifier,
@destinationId uniqueidentifier
AS
BEGIN
SET NOCOUNT ON;
declare @moverLeft int,
@moverRight int,
@destinationRight int,
@node_size int
-- step 0: Initialize parameters.
SELECT
@moverLeft = leftExtent,
@moverRight = rightExtent
FROM
Item
WHERE
id = @id
SELECT
@destinationRight = rightExtent
FROM
Item
WHERE
id = @destinationId
SELECT
@node_size = @moverRight - @moverLeft + 1; -- 'size' of moving node (including all it's sub nodes)
-- step 1: temporary "remove" moving node
UPDATE Item
SET leftExtent = 0-(leftExtent), rightExtent = 0-(rightExtent), updatedDate = GETDATE()
WHERE leftExtent >= @moverLeft AND rightExtent <= @moverRight;
-- step 2: decrease left and/or right position values of currently 'lower' items (and parents)
UPDATE Item
SET leftExtent = leftExtent - @node_size, updatedDate = GETDATE()
WHERE leftExtent > @moverRight;
UPDATE Item
SET rightExtent = rightExtent - @node_size, updatedDate = GETDATE()
WHERE rightExtent > @moverRight;
-- step 3: increase left and/or right position values of future 'lower' items (and parents)
UPDATE Item
SET leftExtent = leftExtent + @node_size, updatedDate = GETDATE()
WHERE leftExtent >= CASE WHEN @destinationRight > @moverRight THEN @destinationRight - @node_size ELSE @destinationRight END;
UPDATE Item
SET rightExtent = rightExtent + @node_size, updatedDate = GETDATE()
WHERE rightExtent >= CASE WHEN @destinationRight > @moverRight THEN @destinationRight - @node_size ELSE @destinationRight END;
-- step 4: move node (and it's subnodes) and update it's parent item id
UPDATE Item
SET
leftExtent = 0-(leftExtent) + CASE WHEN @destinationRight > @moverRight THEN @destinationRight - @moverRight - 1 ELSE @destinationRight - @moverRight - 1 + @node_size END,
rightExtent = 0-(rightExtent) + CASE WHEN @destinationRight > @moverRight THEN @destinationRight - @moverRight - 1 ELSE @destinationRight - @moverRight - 1 + @node_size END,
updatedDate = GETDATE()
WHERE leftExtent <= 0-@moverLeft AND rightExtent >= 0-@moverRight;
UPDATE Item
SET parentId = @destinationId, updatedDate = GETDATE()
WHERE id = @id;
END
元のノードの左右の値 (および後続のすべてのサブノード) を格納するための 2 つの追加の列を使用すると、アルゴリズムを簡素化できると思います。鉛筆と紙を使って例を作成したので、アルゴリズムに穴が見つかった場合はお知らせください。
ターゲット ノード (移動するノードの新しい親) は tNode です。Target Node の左の値は tNode.L で、右の値は tNode.R です。同様に、移動するノードは mNode で、mNode の左右の値は mNode.L と mNode.R です。追加の 2 つの列は、mNode.SL と mNode.SR です。
全体として、R、L、SL、および SR を操作するための 4 つの列があります。
計算する
delta1 = (mNode.R - mNode.L) + 1
mNode の元の L と R を SL と SR 列に保存します
- 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 ;
Do For all Nodes
IF L > mNode.SR
L = L + delta1
IF R > mNode.SR
R = R + delta1
これで、mNode が Tree から切り離され、Tree が mNode なしで調整されます。
計算する
delta2 = (tNode.R - mNode.SL)
Do for all Nodes
IF L >= tNode.R
L = L + delta1
IF R >= tNode.R
R = R + delta1
これで、削除されたノードの数を受け入れるようにツリー (およびターゲット ノード) を調整しました。
mNode を tNode に接続し、SL/SR 列の値をリセットします
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 ;
これらのすべての手順の後、mNode を tNode の下に移動する必要があります。
サブツリーの移動は、ネストされたセットの設計では非常に高価で複雑です。
木を表現するための別のデザインを検討する必要があります。
たとえば、パス列挙設計を使用する場合、各ノードの直接の祖先のリストを連結文字列として保存します。
id path
1 1/
2 1/2/
3 1/3/
4 1/3/4/
5 1/3/5/
次に、サブツリーを移動します (たとえば、ノード 3 がノード 2 の子になるように移動します)。
UPDATE Tree t
JOIN Tree node2 ON (node2.id = 2)
JOIN Tree node3 ON (node3.id = 3)
SET t.path = CONCAT(node2.path, REPLACE(t.path, node3.path, node2.path))
WHERE t.path LIKE CONCAT(node3.path, '%');
ネストされたセット内のノードを新しい親ノードに移動するストアド プロシージャがあります。「somedb」というMySQL / InnoDBデータベースで「category」というテーブルを使用しています。もちろん、移動先が移動したいカテゴリのサブカテゴリである場合、この手順では問題が発生するため、ノードをそれ自体の内部に埋め込もうとしていないことを確認してください。その場合にこの手順を安全にするための演習として、読者に任せます。
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
lft と rgt を負の対応するものに変換するというアイデアに感謝します。このためのより一般的なアプローチをここに投稿しました: Move node in Nested Sets tree。
queryBatch() 関数は、クエリをトランザクションに含めます。
での階層データの保存と使用については、私のブログの記事を参照してくださいMySQL
。
parent
このようなテーブルでブランチ全体を移動するには、ルート(単一の行) を更新するだけです。
関数を作成する必要があります。
CREATE FUNCTION hierarchy_connect_by_parent_eq_prior_id(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE _id INT;
DECLARE _parent INT;
DECLARE _next INT;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;
SET _parent = @id;
SET _id = -1;
IF @id IS NULL THEN
RETURN NULL;
END IF;
LOOP
SELECT MIN(id)
INTO @id
FROM t_hierarchy
WHERE parent = _parent
AND id > _id;
IF @id IS NOT NULL OR _parent = @start_with THEN
SET @level = @level + 1;
RETURN @id;
END IF;
SET @level := @level - 1;
SELECT id, parent
INTO _id, _parent
FROM t_hierarchy
WHERE id = _parent;
END LOOP;
END
クエリで使用します。
SELECT CONCAT(REPEAT(' ', level - 1), CAST(hi.id AS CHAR)) AS treeitem, parent, level
FROM (
SELECT hierarchy_connect_by_parent_eq_prior_id(id) AS id, @level AS level
FROM (
SELECT @start_with := 0,
@id := @start_with,
@level := 0
) vars, t_hierarchy
WHERE @id IS NOT NULL
) ho
JOIN t_hierarchy hi
ON hi.id = ho.id
私はこの投稿が古いことを知っていますが、解決策を見るためにここに来る他のすべての人のためにこの解決策を投稿しています.私はこれを見つけました @ sedna-soft.de. 私はIDをテストし、完全に動作します
-- moves a subtree before the specified position
-- if the position is the rgt of a node, the subtree will be its last child
-- if the position is the lft of a node, the subtree will be inserted before
-- @param l the lft of the subtree to move
-- @param r the rgt of the subtree to move
-- @param p the position to move the subtree before
SET @r: , @l: , @p:
update tree
set
lft = lft + if (@p > @r,
if (@r < lft and lft < @p,
@l - @r - 1,
if (@l <= lft and lft < @r,
@p - @r - 1,
0
)
),
if (@p <= lft and lft < @l,
@r - @l + 1,
if (@l <= lft and lft < @r,
@p - @l,
0
)
)
),
rgt = rgt + if (@p > @r,
if (@r < rgt and rgt < @p,
@l - @r - 1,
if (@l < rgt and rgt <= @r,
@p - @r - 1,
0
)
),
if (@p <= rgt and rgt < @l,
@r - @l + 1,
if (@l < rgt and rgt <= @r,
@p - @l,
0
)
)
)
where @r < @p or @p < @l;
$row は、移動する必要がある行を表す配列です。次のようにする必要があります。
Array ( [lft] => 5 [rgt] => 10 [width] => 6 )
$row2 は、宛先ノードを表す配列です。
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"]);