Han*_*non 1 trigger sql-server referential-integrity
参考我之前关于首选设计的问题,以避免循环或多个更新路径参照完整性通过ON UPDATE CASCADE
和级联更新和删除ON DELETE CASCADE
,我正在尝试使用触发器作为替代方案。
对于这个例子,我试图尽可能地简化设计。
我有一张Parent
桌子和一张Child
桌子;Parent
并Child
共享一列,ParentName
。每当Parent.ParentName
发生变化时,我都想在Child.ParentName
.
够简单吗?如果我更新 中的单行Parent
,我可以使用inserted
和deleted
表很容易地知道要更新Parent
和Child
表中的哪一行。像这样的东西:
UPDATE Parent
SET Parent.ParentName = inserted.ParentName
FROM Parent
INNER JOIN deleted ON Parent.ParentName = deleted.ParentName
CROSS JOIN inserted;
UPDATE Child
SET Child.ParentName = inserted.ParentName
FROM Child
INNER JOIN deleted ON Child.ParentName = deleted.ParentName
CROSS JOIN inserted;
Run Code Online (Sandbox Code Playgroud)
很明显,如果Parent
在一个UPDATE
语句中修改了多于一行,这将不起作用。
表:
IF OBJECT_ID(N'dbo.Parent', N'U') IS NOT NULL
DROP TABLE dbo.Parent;
CREATE TABLE dbo.Parent
(
ParentName int NOT NULL
PRIMARY KEY
, IsActive bit NOT NULL
);
IF OBJECT_ID(N'dbo.Child', N'U') IS NOT NULL
DROP TABLE dbo.Child;
CREATE TABLE dbo.Child
(
ChildName int NOT NULL
PRIMARY KEY
, ParentName int NOT NULL
, IsActive bit NOT NULL
);
GO
INSERT INTO dbo.Parent (ParentName, IsActive)
VALUES (0, 1)
, (1, 1)
, (2, 1);
INSERT INTO dbo.Child (ChildName, ParentName, IsActive)
VALUES (7, 0, 1)
, (8, 1, 1)
, (9, 2, 1);
SELECT *
FROM dbo.Parent;
Run Code Online (Sandbox Code Playgroud)
??????????????????????????? ? 父母名字 ?活跃 ? ??????????????????????????? ? 0 ? 1 ? ? 1 ? 1 ? ? 2 ? 1 ? ???????????????????????????
SELECT *
FROM dbo.Child;
Run Code Online (Sandbox Code Playgroud)
?????????????????????????????????????????? ? 孩子姓名 ? 父母名字 ?活跃 ? ?????????????????????????????????????????? ? 7 ? 0 ? 1 ? ? 8 ? 1 ? 1 ? ? 9 ? 2 ? 1 ? ??????????????????????????????????????????
触发:
CREATE TRIGGER DRI
ON dbo.Parent
INSTEAD OF UPDATE
AS
BEGIN
UPDATE Parent
SET Parent.ParentName = inserted.ParentName
FROM Parent
INNER JOIN deleted ON Parent.ParentName = deleted.ParentName
CROSS JOIN inserted;
UPDATE Child
SET Child.ParentName = inserted.ParentName
FROM Child
INNER JOIN deleted ON Child.ParentName = deleted.ParentName
CROSS JOIN inserted;
END
GO
Run Code Online (Sandbox Code Playgroud)
测试它:
UPDATE dbo.Parent SET ParentName = 6 WHERE ParentName = 2;
Run Code Online (Sandbox Code Playgroud)
结果:
SELECT *
FROM dbo.Parent;
Run Code Online (Sandbox Code Playgroud)
??????????????????????????? ? 父母名字 ?活跃 ? ??????????????????????????? ? 0 ? 1 ? ? 1 ? 1 ? ? 6 ? 1 ? <-- 此行已更改 ???????????????????????????
SELECT *
FROM dbo.Child;
Run Code Online (Sandbox Code Playgroud)
?????????????????????????????????????????? ? 孩子姓名 ? 父母名字 ?活跃 ? ?????????????????????????????????????????? ? 7 ? 0 ? 1 ? ? 8 ? 1 ? 1 ? ? 9 ? 6 ? 1 ? <-- 此行已更改 ??????????????????????????????????????????
但是,如果我更新多行,则会得到以下信息:
UPDATE dbo.Parent SET ParentName = ParentName + 1;
SELECT *
FROM dbo.Parent;
Run Code Online (Sandbox Code Playgroud)
??????????????????????????? ? 父母名字 ?活跃 ? ??????????????????????????? ? 1 ? 1 ? ? 1 ? 1 ? ? 1 ? 1 ? ???????????????????????????
SELECT *
FROM dbo.Child;
Run Code Online (Sandbox Code Playgroud)
?????????????????????????????????????????? ? 孩子姓名 ? 父母名字 ?活跃 ? ?????????????????????????????????????????? ? 7 ? 1 ? 1 ? ? 8 ? 1 ? 1 ? ? 9 ? 1 ? 1 ? ??????????????????????????????????????????
我可以通过使触发器拒绝更新超过一行的更新来解决这个问题,但这不是很可扩展,并且会促进 RBAR。
CREATE TRIGGER DRI
ON dbo.Parent
INSTEAD OF UPDATE
AS
BEGIN
DECLARE @msg nvarchar(1000);
SET @msg = 'Only a single row can be updated per batch.';
IF (SELECT COUNT(1) FROM inserted) > 1
BEGIN
RAISERROR (@msg, 10, 1);
END
ELSE
BEGIN
UPDATE Parent
SET Parent.ParentName = inserted.ParentName
FROM Parent
INNER JOIN deleted ON Parent.ParentName = deleted.ParentName
CROSS JOIN inserted;
UPDATE Child
SET Child.ParentName = inserted.ParentName
FROM Child
INNER JOIN deleted ON Child.ParentName = deleted.ParentName
CROSS JOIN inserted;
END
END
GO
Run Code Online (Sandbox Code Playgroud)
我能做些什么来缓解这种情况吗?显然,我不想在IDENTITY
这些表中添加一列,因为这将否定使用自然键的目的。
我想ROW_NUMBER()
在触发器中使用inserted
和deleted
表上的一种伪列;但是我不能ORDER BY
在OVER(...)
子句中使用“适当的” ,这使得行号不确定:
CREATE TRIGGER DRI
ON dbo.Parent
INSTEAD OF UPDATE
AS
BEGIN
;WITH i AS (
SELECT *
, rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM inserted
)
, d AS (
SELECT *
, rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM deleted
)
UPDATE dbo.Parent
SET Parent.ParentName = i.ParentName
FROM i
INNER JOIN d ON i.rn = d.rn
WHERE Parent.ParentName = d.ParentName
;WITH i AS (
SELECT *
, rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM inserted
)
, d AS (
SELECT *
, rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM deleted
)
UPDATE dbo.Child
SET Child.ParentName = i.ParentName
FROM i
INNER JOIN d ON i.rn = d.rn
WHERE Child.ParentName = d.ParentName
END
GO
Run Code Online (Sandbox Code Playgroud)
考试:
UPDATE dbo.Parent SET ParentName = ParentName + 1;
SELECT *
FROM dbo.Parent;
Run Code Online (Sandbox Code Playgroud)
??????????????????????????? ? 父母名字 ?活跃 ? ??????????????????????????? ? 1 ? 1 ? ? 2 ? 1 ? ? 3 ? 1 ? ???????????????????????????
SELECT *
FROM dbo.Child;
Run Code Online (Sandbox Code Playgroud)
?????????????????????????????????????????? ? 孩子姓名 ? 父母名字 ?活跃 ? ?????????????????????????????????????????? ? 7 ? 1 ? 1 ? ? 8 ? 2 ? 1 ? ? 9 ? 3 ? 1 ? ??????????????????????????????????????????
这似乎确实解决了问题,但我担心它不可靠。
我想
ROW_NUMBER()
在触发器中使用作为一种伪列......
别再想这个了。插入或删除的伪表中的行顺序无法保证。您可能会观察到它“工作”,但您将依赖于无证行为。
缺少用于关联更新行(或更新的伪表或每行触发器等)的公开唯一行标识符的标准解决方法是添加不可变的备用键,例如具有IDENTITY
属性的列. 我意识到这违背了问题的设计目标,但这并没有改变事实。
一rowversion
列也可以工作,但那是 8 个字节,而是误用。
连接物品:
归档时间: |
|
查看次数: |
190 次 |
最近记录: |