SQL 更新触发器上的 Parent Child 关系
SQL Update Parent Child relation on Trigger
我想 link 行中的自连接 table 使用触发器。
在插入根 table 后,我想在 child table 中创建 3 个“级别”。
每个级别都是分层数据(或自连接),例如:
- LVL1
- LVL2
- LVL3
数据库是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 FOR
。
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自加入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
我想 link 行中的自连接 table 使用触发器。 在插入根 table 后,我想在 child table 中创建 3 个“级别”。 每个级别都是分层数据(或自连接),例如:
- LVL1
- LVL2
- LVL3
- LVL2
数据库是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 FOR
。
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自加入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