在层次结构数据库中移动节点 table

Moving nodes in a hierarchy database table

从大局着手:这个问题是层次结构树中拖放节点功能的一部分。

我们有一个名为 Rooms 的 table,它使用 SQL 中的层次结构功能。我们还有一个存储过程,其中 re-parents 一个 child 节点在此 table 中。存储过程采用两个参数,新的 parent id 和 parent 的新 child 节点列表(旧的和新的)。

我们遇到的问题是新children得到的节点值不唯一,问题出现在下面的更新语句中脚本。

注意:此脚本是为了模拟问题而创建的,并不是实际的存储过程:

/** Declare RoomMoveList type **/
CREATE TYPE [RoomMoveList] AS TABLE(
    [RoomId] [int] NOT NULL,
    PRIMARY KEY CLUSTERED 
(
    [RoomId] ASC
)WITH (IGNORE_DUP_KEY = OFF)
)

/** Create Rooms table **/
create table Rooms
(
    RoomId int identity not null,
    RoomName nvarchar(100) not null,
    Node hierarchyid not null,
    NodePath as Node.ToString() persisted not null,
    ParentNode as Node.GetAncestor(1) persisted,
    ParentNodePath as Node.GetAncestor(1).ToString() persisted
)
go

--primary key
alter table Rooms
    add constraint PK_Rooms primary key nonclustered (RoomId)
go

--alternate key
alter table Rooms
    add constraint AK_Rooms unique clustered (Node)
go

--foreign keys
alter table Rooms
    add constraint FK_Rooms_ParentNode_Rooms_Node foreign key (ParentNode) references Rooms(Node)
go

-- insert data
set identity_insert Rooms on
INSERT INTO Rooms (RoomId, RoomName, Node) values
(1, 'Root', '/'),
(2, 'Room 7','/7/'),
(3, 'Room A', '/7/1/'),
(4, 'Booth A1', '/7/1/1/'),
(5, 'Room B', '/7/2/'),
(6, 'Booth B1', '/7/2/1/'),
(7, 'Booth B2', '/7/2/2/'),
(8, 'Booth B3', '/7/2/3/'),
(9, 'Booth B4', '/7/2/4/'),
(10, 'Booth B5', '/7/2/5/'),
(11, 'Room C', '/7/3/'),
(12, 'Booth C1', '/7/3/1/'),
(13, 'Booth C2', '/7/3/2/'),
(14, 'Booth C3', '/7/3/3/')
set identity_insert Rooms off

/* Here's the first part of the actual stored procedure */
declare
    @newParentNode hierarchyid,
    @newNode hierarchyid, -- This is a new node that currently does not exist in rooms
    @nodeToMove hierarchyid,
    @newParentRoomId int,
    @roomMoves as RoomMoveList;

    -- these are the input values for the stored procedure that we simulate here.
    set @newParentRoomId = 3
    insert into @roomMoves (RoomId) values
    (4), (5), (11)

    set transaction isolation level serializable

begin tran
    -- First select Node from parent id, and then get next new descendant
    select @newParentNode = Node from Rooms where RoomId = @newParentRoomId

    select @newNode = @newParentNode.GetDescendant(max(Node), null)
    from Rooms
    where Node.GetAncestor(1) = @newParentNode;

    -- Need to cache these since we'll use them multiple times in the real query
    select f.RoomId
            , IIF(f.ParentNode <> @newParentNode, 1, 0) as Moved
    into #TempMoves
    from Rooms f
                join @roomMoves fm
                    on fm.RoomId = f.RoomId;

    /* This is the failing update statement. */
    -- Update nodes, but only the moved nodes
    ;with CTE_ROOM_NODES as (
        select f.RoomId
                , f.Node
                , f.NodePath
        from Rooms f
                    join #TempMoves fm on fm.RoomId = f.RoomId
        where fm.Moved = 1
    )
    update Rooms
    set Node      = Rooms.Node.GetReparentedValue(ctn.Node, @newNode)
    from CTE_ROOM_NODES ctn
    where Rooms.Node.IsDescendantOf(ctn.Node) = 1;

    /* The stored procedure continues here but I'm leaving that out since the error is in the update statement above */

commit tran

select * from Rooms

drop table #TempMoves
drop table Rooms
drop type RoomMoveList

更多信息:

这是位置移动前后的对比。我们点击并拖动房间 A 成为房间 7 的兄弟,而展位 A1 将成为房间 B 和房间 C 的兄弟。

__________

如果我遗漏了什么或者我是否可以改进问题,请告诉我!

您需要重新计算移动元素的 hierarchyids 相对于它们的新位置(在层次结构中的位置)。

@newNode = @newParentNode.GetDescendant(max(Node), null) -- this returns the maximum node in the new location

Rooms.Node.GetReparentedValue(ctn.Node, @newNode) --every moved room is re-parented to @newNode. 

当移动多个 elements/rooms 时,它们的重新父级值可以重叠(或与新父级的现有子级重叠)。 例如。

B1 = "B/1", C1 = "C/1"
if they get re-parented to A:
B1 = "A/1", C1 = "A/1" and constraint conflict

/* Here's the first part of the actual stored procedure */
declare
    @newParentNode hierarchyid,
    @newNode hierarchyid, -- This is a new node that currently does not exist in rooms
    @nodeToMove hierarchyid,
    @newParentRoomId int,
    @roomMoves as RoomMoveList;

-- these are the input values for the stored procedure that we simulate here.
set @newParentRoomId = 3
insert into @roomMoves (RoomId) values
(4), (5), (11)

set transaction isolation level serializable

begin tran   


    -- First select Node from parent id, and then get next new descendant
    select @newParentNode = Node from Rooms where RoomId = @newParentRoomId

    select @newNode = @newParentNode.GetDescendant(max(Node), null)
    from Rooms
    where Node.GetAncestor(1) = @newParentNode;
    -- Need to cache these since we'll use them multiple times in the real query

--###### this will work if nodes are numeric /1/2/3/ , and not '/1/2.1/3/'
    --get the ordinal of the max node
    declare @movedroomsstartordinal int;
    select @movedroomsstartordinal = try_cast(replace(replace(isnull(max(Node).ToString(), @newParentNode.ToString()), @newParentNode.ToString(), ''), '/', '') as int)
    from Rooms
    where Node.GetAncestor(1) = @newParentNode;


    select RoomId, Moved, 
        --increase the ordinal of each moved member (max existing + ordinal of each new member)
        try_cast(concat(@newParentNode.ToString(), case Moved when 1 then @movedroomsstartordinal + rownumofmovedroom else null end, '/') as hierarchyid) as thenewnode
    into #TempMoves
    from
    (
    select f.RoomId
            , IIF(f.ParentNode <> @newParentNode, 1, 0) as Moved,
            row_number() over(partition by IIF(f.ParentNode <> @newParentNode, 1, 0) order by f.RoomId) as rownumofmovedroom
    from Rooms f
                join @roomMoves fm
                    on fm.RoomId = f.RoomId
    ) as src;

--check if anything is wrong
--if exists(select * from #TempMoves where moved=1 and thenewnode is null)....
--raiserror etc..

    -- Update nodes, but only the moved nodes
    ;with CTE_ROOM_NODES as (
        select f.RoomId
                , f.Node
                , f.NodePath
                , fm.thenewnode
        from Rooms f
                    join #TempMoves fm on fm.RoomId = f.RoomId
        where fm.Moved = 1
    )
    update Rooms
    set Node      = Rooms.Node.GetReparentedValue(ctn.Node, thenewnode)
    from CTE_ROOM_NODES ctn
    where Rooms.Node.IsDescendantOf(ctn.Node) = 1;

    /* The stored procedure continues here but I'm leaving that out since the error is in the update statement above */

commit tran