Sql server 从层次结构中删除项目
我有一些这样的树结构 树中的某些节点将被禁用。禁用节点的子节点需要连接到禁用节点的父节点。如果禁用该选项,则将其添加到以下父级,依此类推 在此图片中,节点2被禁用,节点4和5连接到节点2的父节点,即节点1 这就是结果 一个更复杂的案例id,其中禁用节点的父节点也被禁用 在这个示例中,我获得了一些testdata的所有子节点,但我不知道是否可以删除禁用的节点,但保持子节点连接到下一个可能的父节点Sql server 从层次结构中删除项目,sql-server,tsql,tree,Sql Server,Tsql,Tree,我有一些这样的树结构 树中的某些节点将被禁用。禁用节点的子节点需要连接到禁用节点的父节点。如果禁用该选项,则将其添加到以下父级,依此类推 在此图片中,节点2被禁用,节点4和5连接到节点2的父节点,即节点1 这就是结果 一个更复杂的案例id,其中禁用节点的父节点也被禁用 在这个示例中,我获得了一些testdata的所有子节点,但我不知道是否可以删除禁用的节点,但保持子节点连接到下一个可能的父节点 CREATE TABLE #Node ( Id INT, ParentI
CREATE TABLE #Node
(
Id INT,
ParentID INT,
Name NVARCHAR(20),
skipNode BIT
);
INSERT INTO #Node
VALUES (1, NULL, 'node-1', 0),
(2, 1, 'node-2', 1),
(3, 1, 'node-3', 0),
(4, 2, 'node-4', 0),
(5, 2, 'node-5', 0),
(6, 3, 'node-6', 0),
(7, 4, 'node-6', 0),
(8, 4, 'node-6', 0);
WITH RCTE AS
(
SELECT
anchor.Id AS ItemId,
skipNode,
anchor.ParentId AS ItemParentId,
1 AS Lvl,
anchor.[Name],
CAST(name AS VARCHAR(1000)) AS NodePath
FROM
#Node anchor
WHERE
anchor.[Id] = 1
UNION ALL
SELECT
nextDepth.Id AS ItemId,
nextDepth.skipNode,
nextDepth.ParentId AS ItemParentId,
Lvl+1 AS Lvl,
nextDepth.[Name],
CAST((rec.NodePath + '/' + nextDepth.[Name]) AS VARCHAR(1000)) AS NodePath
FROM
#Node nextDepth
INNER JOIN
RCTE rec ON nextDepth.ParentId = rec.ItemId
)
SELECT ItemId, skipNode , ItemParentId, [Name], NodePath
FROM RCTE AS hierarchy
DROP TABLE #Node
禁用节点2的预期结果为
ItemId skipNode ItemParentId Name
----------- -------- ------------ -------
1 0 NULL node-1
3 0 1 node-3
6 0 3 node-6
4 0 1 node-4
5 0 1 node-5
7 0 4 node-6
8 0 4 node-6
这个例子应该能让你明白:
USE tempdb;
GO
DROP FUNCTION IF EXISTS dbo.GetParentNode;
GO
CREATE FUNCTION dbo.GetParentNode
(
@NodePath varchar(max)
)
RETURNS int
AS
BEGIN
/*
SELECT dbo.GetParentNode('12/13/14');
SELECT dbo.GetParentNode('12/14');
*/
DECLARE @ReturnValue int;
DECLARE @StringToProcess varchar(max) = REVERSE(@NodePath);
DECLARE @DelimiterLocation int;
SET @DelimiterLocation = CHARINDEX('/', @StringToProcess);
IF @DelimiterLocation > 0
BEGIN
SET @StringToProcess = SUBSTRING(@StringToProcess, @DelimiterLocation + 1, LEN(@StringToProcess));
SET @DelimiterLocation = CHARINDEX('/', @StringToProcess);
IF @DelimiterLocation = 0
BEGIN
SET @ReturnValue = CAST(REVERSE(@StringToProcess) AS int);
END ELSE BEGIN
SET @ReturnValue = CAST(REVERSE(LEFT(@StringToProcess, @DelimiterLocation - 1)) AS int);
END;
END;
RETURN @ReturnValue;
END;
GO
DROP TABLE IF EXISTS dbo.Nodes;
GO
CREATE TABLE dbo.Nodes
(
NodeID int,
ParentNodeID int,
NodeName nvarchar(20),
IsDisabled bit
);
INSERT dbo.Nodes
(
NodeID, ParentNodeID, NodeName, IsDisabled
)
VALUES (1, NULL, 'node-1', 0),
(2, 1, 'node-2', 1),
(3, 1, 'node-3', 0),
(4, 2, 'node-4', 0),
(5, 2, 'node-5', 0),
(6, 3, 'node-6', 0),
(7, 4, 'node-6', 0),
(8, 4, 'node-6', 0);
WITH AllNodes AS
(
SELECT toplevel.NodeID,
toplevel.IsDisabled,
toplevel.ParentNodeID,
1 AS NodeLevel,
toplevel.NodeName,
CAST(toplevel.NodeID AS varchar(max)) AS NodePath
FROM dbo.Nodes AS toplevel
WHERE toplevel.NodeID = 1
UNION ALL
SELECT
n.NodeID,
n.IsDisabled,
n.ParentNodeID,
an.NodeLevel + 1,
n.NodeName,
an.NodePath + CASE WHEN n.IsDisabled = 0
THEN '/' + CAST(n.NodeID AS varchar(max))
ELSE ''
END
FROM dbo.Nodes AS n
INNER JOIN AllNodes AS an
ON an.NodeID = n.ParentNodeID
)
SELECT an.NodeID, an.IsDisabled, an.NodeName,
an.ParentNodeID, an.NodeLevel, an.NodePath,
dbo.GetParentNode(an.NodePath) AS TrueParentNodeID
FROM AllNodes AS an
WHERE an.IsDisabled = 0;
DROP TABLE dbo.Nodes;
GO
只是执行整个过程,看看它看起来是否正确。希望能有所帮助。感谢您提供了一个有效的示例。我使用了您所拥有的,并对其进行了修改,以使用hierarchyid数据类型(这使您的工作变得非常简单。首先,修改后的设置代码:
WITH RCTE AS
(
SELECT
anchor.Id AS ItemId,
skipNode,
anchor.ParentId AS ItemParentId,
1 AS Lvl,
anchor.[Name],
CAST(concat('/', id, '/') AS VARCHAR(1000)) AS NodePath
FROM
#Node anchor
WHERE
anchor.[Id] = 1
UNION ALL
SELECT
nextDepth.Id AS ItemId,
nextDepth.skipNode,
nextDepth.ParentId AS ItemParentId,
Lvl+1 AS Lvl,
nextDepth.[Name],
CAST(concat(rec.NodePath, nextDepth.[id], '/') AS VARCHAR(1000)) AS NodePath
FROM
#Node nextDepth
INNER JOIN
RCTE rec ON nextDepth.ParentId = rec.ItemId
)
SELECT ItemId, skipNode , ItemParentId, [Name], cast(NodePath as [hierarchyid]) as NodePath
into dbo.rcte
FROM RCTE AS hierarchy
GO
仅有的两个修改是NodePath的格式(现在是以斜杠分隔的ID列表,而不是名称)和将结果转储到表中(而不是原始结果集)。现在是一个存储过程,它接收ItemID并将其从表中删除(相应地更新它所依赖的任何内容)
请注意,如果我真的在编写存储过程,我会有更多的内容(即检查我试图删除的内容是否确实存在,检查父对象是否存在,等等),但解决方案的要点就在那里
create or alter procedure RemoveItem (
@ItemID int
)
AS
begin
set nocount on;
declare @NewParent hierarchyid = (
select parent.NodePath
from dbo.rcte as child
join dbo.rcte as parent
on child.ItemParentId = parent.ItemId
where child.ItemId = @ItemID
), @OldParent hierarchyid = (
select Child.NodePath
from dbo.rcte as child
where child.ItemId = @ItemID
), @NewParentItemID int = (
select Child.ItemParentId
from dbo.rcte as child
where child.ItemId = @ItemID
);
update r
set NodePath = NodePath.GetReparentedValue(@OldParent, @NewParent),
ItemParentId = case
when ItemParentId = @ItemID
then @NewParentItemID
else ItemParentId
end
from dbo.rcte as r
where NodePath.IsDescendantOf(@OldParent) = 1
and r.ItemId <> @ItemID;
delete dbo.rcte
where ItemId = @ItemID;
end
go
exec dbo.RemoveItem @ItemID = 2;
select *, NodePath.ToString()
from dbo.rcte;