Удалить элементы из иерархии - PullRequest
0 голосов
/ 30 октября 2019

У меня есть какая-то древовидная структура, подобная этой

enter image description here

Некоторые узлы в дереве будут отключены. Дочерние узлы отключенных узлов должны быть подключены к родительскому узлу отключенного узла. Если этот отключен для следующего родителя и т. Д.

На этом рисунке узел 2 отключен, и узлы 4 и 5 подключаются к родителю узла 2, который является узлом 1

enter image description here

Это будет результат

enter image description here

Более сложный идентификатор случая, гдеродитель отключенного узла также отключен

enter image description hereenter image description here

В этом примере я получаю все дочерние узлы некоторых тестовых данныхно я понятия не имею, можно ли отбросить отключенные узлы, но оставить дочерние узлы подключенными к следующему возможному родителю

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 

Ответы [ 2 ]

1 голос
/ 30 октября 2019

Этот пример должен помочь вам:

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

Просто выполните все это и посмотрите, выглядит ли это правильно. Надеюсь, это поможет.

0 голосов
/ 30 октября 2019

Спасибо за предоставленный рабочий пример. Я взял то, что у вас было, и изменил его, чтобы он работал с типом данных иерархии (что делает то, что вы пытаетесь сделать, довольно просто. Во-первых, измененный установочный код:

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 (теперь это разделенный слэшем список идентификаторов вместо имен) и вывод результатов в таблицу (вместо необработанного набора результатов). Теперь хранимая процедура, которая получает 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

После этого я могу просто вызвать sproc с узлом, который хочу удалить:

exec dbo.RemoveItem @ItemID = 2;

select *, NodePath.ToString()
from dbo.rcte;

Примечание,если бы я писал этот sproc по-настоящему, у меня было бы больше (т.е. проверить, существует ли на самом деле то, что я пытаюсь удалить, проверить, существует ли parent и т. д.), но костирешение есть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...