SQL 更新触发器上的 Parent Child 关系

SQL Update Parent Child relation on Trigger

我想 link 行中的自连接 table 使用触发器。 在插入根 table 后,我想在 child table 中创建 3 个“级别”。 每个级别都是分层数据(或自连接),例如:

数据库是SQLSERVER。 我知道有大量 material 关于 self-joined 和分层 SQL 数据,但是......我不知道我没有找到我所期望的。我花了太多时间尝试解决方案和在线搜索。

A link 到 SQLFiddle.

这里是基本架构,例如:

CREATE TABLE [dbo].[Root] (
    [RootID] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,
    [Name] [varchar](50)
)
GO

CREATE TABLE [dbo].[Child] (
    [ChildID] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,
    [RootID] [int],
    [Name] [varchar](50),
    [ParentID] [int]
)
GO

ALTER TABLE [dbo].[Child] WITH CHECK ADD CONSTRAINT [Child_RootID_FK] FOREIGN KEY([RootID])
REFERENCES [dbo].[Root] ([RootID]) ON DELETE SET NULL
GO

ALTER TABLE [dbo].[Child] WITH CHECK ADD CONSTRAINT [Child_ParentID_FK] FOREIGN KEY([ParentID])
REFERENCES [dbo].[Child] ([ChildID])
GO

CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON
    
    INSERT INTO [dbo].[Child] ([RootID], [Name])
    SELECT
        I.[RootID],
        CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
    FROM INSERTED I
    CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)

END
GO


INSERT INTO [dbo].[Root] ([Name]) VALUES (
    'Foo'
)

SELECT * FROM [Root]
SELECT * FROM [Child]

我有当前结果:

ChildID RootID Name ParentID
1 1 Foo_1 NULL
2 1 Foo_2 NULL
3 1 Foo_3 NULL

预期结果为:

ChildID RootID Name ParentID
1 1 Foo_1 NULL
2 1 Foo_2 1
3 1 Foo_3 2

我不确定如何实现。 我找到了一个涉及使用 SEQUENCE

的接近答案 (here)

解决方案可能是这样的:

DROP TRIGGER [dbo].[Root_TR]
GO

CREATE SEQUENCE [dbo].[Sequence] START WITH 1 INCREMENT BY 1
GO

CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON
    
    ALTER SEQUENCE [dbo].[Sequence] RESTART

    DECLARE @map TABLE ([ID] [int], [Seq] [int])

    INSERT INTO [dbo].[Child] ([RootID], [Name])
    OUTPUT [inserted].ChildID, NEXT VALUE FOR [dbo].[Sequence] INTO @map
    SELECT
        I.[RootID],
        CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
    FROM INSERTED I
    CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)


    UPDATE C 
        SET C.[ParentID] = CASE 
            WHEN M.Seq = 1
                THEN NULL
            ELSE
                (SELECT [Id] FROM @map WHERE [Seq] = [Seq] - 1)
            END
    FROM [dbo].Child C
    INNER JOIN @map M ON C.ChildID = M.ID

END
GO

不幸的是,在 OUTPUT 子句中不允许使用 NEXT VALUE FORError 11720 NEXT VALUE FOR function is not allowed in the TOP, OVER, OUTPUT, ON, WHERE, GROUP BY, HAVING, or ORDER BY clauses.

我不能依赖 [Name] 列来执行 UPDATE SET [ParentID] = ... FROM ... JOIN ...

有很多关于SQL自加入table的答案,但我真的找不到答案,而且我对SQL的了解有限。

我试过这样的事情

CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON
    
    ALTER SEQUENCE [dbo].[Sequence] RESTART

    DECLARE @map TABLE ([ChildID] [int], [Seq] [int])

    DECLARE @i int = NEXT VALUE FOR [Sequence]

    INSERT INTO [dbo].[Child] ([RootID], [Name])
    OUTPUT [inserted].ChildID, @i AS [Seq] INTO @map
    SELECT
        I.[RootID],
        CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
    FROM INSERTED I
    CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)

    DECLARE @xml xml = (SELECT * FROM @map FOR XML AUTO)
    PRINT CONVERT(nvarchar(max), @xml)

    UPDATE C 
        SET C.[ParentID] = CASE 
            WHEN M.Seq = 1
                THEN NULL
            ELSE
                (SELECT [ChildID] FROM @map WHERE [Seq] = [Seq] - 1)
            END
    FROM [dbo].Child C
    INNER JOIN @map M ON C.ChildID = M.ChildID

END
GO

不幸的是,临时 TABLE @map 没有正确填写。 @i 被调用一次,并且不会为每个输出行递增。

ChildID Seq
10 1
11 1
12 1

另一种尝试是使用 DEFAULT。但是我在创建触发器时出错:“列名或提供的值的数量与 table 定义不匹配。”。可能是因为 OUTPUT 子句在 @map TABLE 中看到的可用列多于 OUTPUT.

中的列数
CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON
    
    ALTER SEQUENCE [dbo].[Sequence] RESTART

    DECLARE @map TABLE (
        [Seq] [int] PRIMARY KEY NOT NULL DEFAULT (NEXT VALUE FOR [Sequence]),
        [ChildID] [int]
    )

    INSERT INTO [dbo].[Child] ([RootID], [Name])
    OUTPUT [inserted].ChildID INTO @map
    SELECT
        I.[RootID],
        CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
    FROM INSERTED I
    CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)

    DECLARE @xml xml = (SELECT * FROM @map FOR XML AUTO)
    PRINT CONVERT(nvarchar(max), @xml)

    UPDATE C 
        SET C.[ParentID] = CASE 
            WHEN M.Seq = 1
                THEN NULL
            ELSE
                (SELECT [ChildID] FROM @map WHERE [Seq] = [Seq] - 1)
            END
    FROM [dbo].Child C
    INNER JOIN @map M ON C.ChildID = M.ChildID
    
END
GO

而最后的尝试是用一个#table(@xTABLE、TABLE#x、TABLEx我没分清楚)。但我想将临时 table 的范围限定为触发器,而不是使 table 全局可用。 所以,这次创建了 TRIGGER。但是当被触发时,错误消息是 Invalid object name 'Sequence'。我不知道为什么 [Sequence] object 在这个 #table.

中不可用
CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON
    
    ALTER SEQUENCE [dbo].[Sequence] RESTART
    
    CREATE TABLE #map (
        [Seq] [int] PRIMARY KEY NOT NULL DEFAULT (NEXT VALUE FOR [Sequence]),
        [ChildID] [int] NOT NULL
    )
    
    INSERT INTO [dbo].[Child] ([RootID], [Name])
    OUTPUT [inserted].ChildID INTO #map
    SELECT
        I.[RootID],
        CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
    FROM INSERTED I
    CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)

    DECLARE @xml xml = (SELECT * FROM #map FOR XML AUTO)
    PRINT CONVERT(nvarchar(max), @xml)

    UPDATE C 
        SET C.[ParentID] = CASE 
            WHEN M.Seq = 1
                THEN NULL
            ELSE
                (SELECT [ChildID] FROM #map WHERE [Seq] = [Seq] - 1)
            END
    FROM [dbo].Child C
    INNER JOIN #map M ON C.ChildID = M.ChildID

END
GO

所以最后我还没有任何解决方案来正确 link 我的不同级别与他们 parents。 任何帮助将不胜感激。

这里是完整的触发器。

CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON
    
    CREATE TABLE #map (
        [Seq] [int] PRIMARY KEY NOT NULL IDENTITY(1,1)
        [ChildID] [int] NOT NULL
    )
    
    INSERT INTO [dbo].[Child] ([RootID], [Name])
    OUTPUT INSERTED.ChildID INTO #map
    SELECT
        I.[RootID],
        CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
    FROM INSERTED I
    CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)

    UPDATE C 
        SET C.[ParentID] = CASE 
            WHEN M.[Seq] = 1
                THEN NULL
            ELSE
                (SELECT [ChildID] FROM #map WHERE [Seq] = M.[Seq] - 1)
            END
    FROM [dbo].Child C
    INNER JOIN #map M ON C.ChildID = M.ChildID

END
GO