我有一张桌子,让我们说一下TestTable。此表格包含以下列:
ID1 | ID2 | ID3 | LEVEL | PARENT_LEVEL | ENABLED | OBSOLET
所有列都是整数,ENABLED和OBSOLET只有两个可能的值(0或1)
LEVEL列可以有父级别,父级别可以是另一个父级别,依此类推,例如,想象下面的表格内容:
ID1 | ID2 | ID3 | LEVEL | PARENT_LEVEL | ENABLED | OBSOLET
1 6 7 98 NULL 1 0
1 6 6 99 98 1 0
1 4 6 100 99 1 0
1 2 3 200 100 1 0
2 4 1 300 NULL 0 0
3 3 4 400 NULL 0 1
3 4 5 500 400 0 0
ID1,ID2和ID3是主键。
所以在树中代表这个:
+ 98
|__ 99
|__ 100
|___ 200
+ 300
+ 400
|__ 500
200有100作为父母,100有99作为父母,99有98作为父母。 300没有父母。 500有400作为父母,400有没有父母。
所以我需要的是一个更新查询,以递归方式更新字段' ENABLED',例如:
如果我使用ENABLED = 1更新LEVEL 99,他的父母98必须更新为ENABLED = 1但不能更新为100和200.
如果我使用ENABLED = 1更新LEVEL 200,他的父母100必须更新为ENABLED = 1,还有LEVEL 99和98,因为他们也有父母。
如果我使用ENABLED = 1更新LEVEL 300,则只更新LEVEL 300,因为它没有父级。
所以我需要一个递归更新查询来更新字段ENABLED,直到LEVEL没有父节点(PARENT_LEVEL)。此外,我需要使用一个更新查询一次更新所有级别,而不仅仅执行更新的混凝土级别。
此外,在每次更新时,我需要检查字段' OBSOLET',如果LEVEL将字段OBSOLET设置为1,则意味着必须进行回滚,例如,在表格内容之上考虑,如果我将LEVEL 500更新为ENABLED = 1,没问题因为它的OBSOLET字段为0,所以它的字段ENABLED设置为1,然后通过递归,我们尝试将其父级LEVEL 400更新为ENABLED = 1,但是它的OBSOLET字段设置为1表示需要进行回滚,即LEVEL 400的ENABLED字段保持为0(未更新),设置为1的500级字段ENABLED也应恢复为0。
最后一个问题是此更新查询应该在此表TestTable的触发器中:
CREATE TRIGGER [dbo].[TG_TestTable]
ON [dbo].[TestTable]
FOR UPDATE
AS
IF UPDATE ([ENABLED])
BEGIN
// Update query must be here, so if field ENABLED is updated, trigger is fired again...so I don't know if disable trigger statement is necessary to be done before this update query and enable trigger after it.
END
这是因为要激活触发器,会对表TestTable的某些行执行更新,例如:
UPDATE [dbo].[TestTable]
SET ENABLED = 1
WHERE
LEVEL IN (100,300,500);
所以我试图在触发器中进行更新查询,但我不知道如何完成它:
UPDATE [dbo].[TestTable]
SET ENABLED= inserted.ENABLED
..... // SOMETHING ELSE
FROM inserted
WHERE
[dbo].[TestTable].ID1 = inserted.ID1
AND
[dbo].[TestTable].ID2 = inserted.ID2
AND
[dbo].[TestTable].ID3 = inserted.ID3
AND
[dbo].[TestTable].PARENT_LEVEL = inserted.LEVEL;
那我怎么能做到这一点?也许使用递归函数或递归CTE?或者在时间执行和性能方面更好地在同一个表上进行递归触发?欢迎所有的想法。
答案 0 :(得分:1)
我找到了解决方法。这是一个很长的问题,但它很容易理解 首先,创建并填充样本表(请将此步骤保存在您未来的问题中)
CREATE TABLE TestTable
(
ID1 int NOT NULL,
ID2 int NOT NULL,
ID3 int NOT NULL,
LEVEL int NOT NULL,
PARENT_LEVEL int,
ENABLED int,
OBSOLET int,
PRIMARY KEY (ID1, ID2, ID3)
)
INSERT INTO TestTable VALUES
(1, 6, 7, 98, NULL, 1, 0),
(1, 6, 6, 99, 98 , 0, 0),
(1, 4, 6, 100, 99 , 0, 0),
(1, 2, 3, 200, 100 , 0, 0),
(2, 4, 1, 300, NULL, 0, 0),
(3, 3, 4, 400, NULL, 0, 1),
(3, 4, 5, 500, 400 , 0, 0)
然后,创建INSTEAD OF UPDATE trigger
,只会更新符合条件的记录
注意:这也会更新未更改启用值的记录,您很快就会在代码中看到它。
CREATE TRIGGER tr_TestTable_IOU ON TestTable
INSTEAD OF UPDATE
AS
;WITH CTE AS
( -- A recursive cte to get all the parents of the updated records
SELECT i.ID1,
i.ID2,
i.ID3,
i.LEVEL,
i.PARENT_LEVEL,
i.ENABLED,
i.OBSOLET
FROM inserted i
INNER JOIN deleted d ON i.ID1 = d.ID1
AND i.ID2 = d.ID2
AND i.ID3 = d.ID3
WHERE i.ENABLED = 1
AND d.ENABLED = 0
-- The where clause will allow only records where enabled was changed from 0 to 1
UNION ALL
SELECT t.ID1,
t.ID2,
t.ID3,
t.LEVEL,
t.PARENT_LEVEL,
t.ENABLED,
t.OBSOLET
FROM TestTable t
INNER JOIN CTE ON t.LEVEL = CTE.PARENT_LEVEL
), CTE_OBSOLET AS
( -- A second recursive cte to get all the records where at least in one parent the value of OBSOLET = 1
SELECT i.ID1,
i.ID2,
i.ID3,
i.LEVEL,
i.PARENT_LEVEL,
i.ENABLED,
i.OBSOLET
FROM TestTable i
WHERE OBSOLET = 1
UNION ALL
SELECT t.ID1,
t.ID2,
t.ID3,
t.LEVEL,
t.PARENT_LEVEL,
t.ENABLED,
1
FROM TestTable t
INNER JOIN CTE_OBSOLET ON t.PARENT_LEVEL = CTE_OBSOLET.LEVEL
)
-- Update the enabled column to all relevant records (including parents)
UPDATE t
SET ENABLED = 1
FROM TestTable t
INNER JOIN CTE ON t.ID1 = CTE.ID1
AND t.ID2 = CTE.ID2
AND t.ID3 = CTE.ID3
LEFT JOIN CTE_OBSOLET ON t.ID1 = CTE_OBSOLET.ID1
AND t.ID2 = CTE_OBSOLET.ID2
AND t.ID3 = CTE_OBSOLET.ID3
WHERE CTE_OBSOLET.LEVEL IS NULL -- Assuming the LEVEL is not nullable. Any other not nullable column can be used here
-- Update records where columns other then ENABLED was changed.
-- Since this is an instead of update trigger, you have to include this to enable updates on other columns.
-- This assumes that you can't update the columns of the primary key (ID1, ID2 and ID3).
UPDATE t
SET LEVEL = i.LEVEL,
PARENT_LEVEL = i.PARENT_LEVEL,
OBSOLET = i.OBSOLET
FROM TestTable t
INNER JOIN inserted i ON t.ID1 = i.ID1
AND t.ID2 = i.ID2
AND t.ID3 = i.ID3
INNER JOIN deleted d ON i.ID1 = d.ID1
AND i.ID2 = d.ID2
AND i.ID3 = d.ID3
WHERE i.LEVEL <> d.LEVEL
OR d.PARENT_LEVEL <> i.PARENT_LEVEL
OR d.OBSOLET <> i.OBSOLET
GO
SELECT *
FROM TestTable
结果:
ID1 ID2 ID3 LEVEL PARENT_LEVEL ENABLED OBSOLET
1 6 7 98 NULL 1 0
1 6 6 99 98 0 0
1 4 6 100 99 0 0
1 2 3 200 100 0 0
2 4 1 300 NULL 0 0
3 3 4 400 NULL 0 1
3 4 5 500 400 0 0
做一些更新:
UPDATE TestTable
SET ENABLED = 1
WHERE LEVEL IN(200, 500)
UPDATE TestTable
SET ENABLED = 1,
OBSOLET = 1
WHERE LEVEL = 500
测试结果:
SELECT *
FROM TestTable
结果:
ID1 ID2 ID3 LEVEL PARENT_LEVEL ENABLED OBSOLET
1 6 7 98 NULL 1 0
1 6 6 99 98 1 0
1 4 6 100 99 1 0
1 2 3 200 100 1 0
2 4 1 300 NULL 0 0
3 3 4 400 NULL 0 1
3 4 5 500 400 0 1