【问题标题】:SQL Update Parent Child relation on TriggerSQL在触发器上更新父子关系
【发布时间】:2021-10-15 10:12:37
【问题描述】:

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

  • LVL1
    • LVL2
      • LVL3

数据库是 SQLSERVER。 我知道有大量关于自联接和分层 SQL 数据的材料,但是……我不知道我没有找到我期望的东西。我花了太多时间尝试解决方案和在线搜索。

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

很遗憾,NEXT VALUE FOR 不允许在 OUTPUT 子句中使用。 Error 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 自联接表有很多答案,但我真的找不到答案,而且我对 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。但是在创建触发器时出现错误:“列名或提供的值的数量与表定义不匹配。”。可能是因为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(我没有清楚地区分@x TABLE、TABLE #x、TABLE x)。但我想将临时表的范围限定为触发器,而不是使该表全局可用。 所以,这次TRIGGER 被创建了。但是当被触发时,错误消息是Invalid object name 'Sequence'。我不知道为什么这个#table 中没有[Sequence] 对象。

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

所以最后我还没有任何解决方案可以正确地将我的不同级别与他们的父母联系起来。 任何帮助将不胜感激。

【问题讨论】:

  • 为什么不在表变量上使用IDENTITY 列,而不是SEQUENCE
  • 为什么在插入值后需要创建 2 个“孩子”?另外,为什么还要将根插入到子表中?当然根应该只是在根表中?如果需要在子表中,为什么根表存在?这只是复制数据,没有必要这样做。老实说,这感觉有点像 XY 问题。
  • 哼,这肯定是个主意。我受到提供的链接的启发。至少表格变量填写正确,但我的UPDATE 语句暂时不起作用。
  • 可能是设计的不好,或者这个例子太多简化了,我不知道。例如,这可能是硬盘驱动器,..e C:, D: ... 在 Windows 上。子表可能是链接到驱动器的嵌套文件夹结构。所以我有 C:\Folder1、C:\Folder1\Folder2、C:\Folder3。这只是一个示例,与我的真实对象无关,但如果设计错误,我想知道为什么@Larnu。
  • 我目前没有足够的信息,但是通过触发器创建子节点,并且总是 2,感觉很奇怪,并且复制数据错误的。

标签: sql-server sql-update parent-child hierarchy self-join


【解决方案1】:

这里是完整的触发器。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多