在层次结构数据库中移动节点 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
更多信息:
如果你注释掉AK_Rooms和FK_Rooms_ParentNode_Rooms_Node这两个约束,那么你''ll see the actual faulty table with non unique values.
如果您从模拟输入值中删除 id 11,一切正常。因为第一个新的child节点会得到一个唯一的值,但是第二个新的child节点会得到和第一个新的child节点相同的值。
这是位置移动前后的对比。我们点击并拖动房间 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
从大局着手:这个问题是层次结构树中拖放节点功能的一部分。
我们有一个名为 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
更多信息:
如果你注释掉AK_Rooms和FK_Rooms_ParentNode_Rooms_Node这两个约束,那么你''ll see the actual faulty table with non unique values.
如果您从模拟输入值中删除 id 11,一切正常。因为第一个新的child节点会得到一个唯一的值,但是第二个新的child节点会得到和第一个新的child节点相同的值。
这是位置移动前后的对比。我们点击并拖动房间 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