【问题标题】:How to update in INSTEAD OF UPDATE trigger for view, created for linked tables如何在为链接表创建的视图的 INSTEAD OF UPDATE 触发器中更新
【发布时间】:2020-12-25 16:37:37
【问题描述】:

我有表 GroupRole,用外键链接。我需要通过视图更新它们,所以我必须编写 INSTEAD OF UPDATE 触发器。我目前的解决方案是错误的,我不知道如何解决它。我该如何解决?

CREATE TABLE [Group]
(
    groupId   INT PRIMARY KEY IDENTITY,
    groupName VARCHAR(50) NOT NULL,
    picture   IMAGE       NULL,
    CONSTRAINT UniqueGroupName UNIQUE (groupName)
)

表格角色

CREATE TABLE Role
(
    roleName VARCHAR(50),
    groupId  INT,
    CONSTRAINT FK_Group FOREIGN KEY (groupId) REFERENCES [Group] (groupId),
    PRIMARY KEY (roleName, groupId),
    canBan   TINYINT DEFAULT (0) /* 0 or 1 */
)

查看

CREATE VIEW GroupRoleView AS
SELECT g.groupName, r.roleName, r.canBan
FROM [Group] as g
         INNER JOIN Role AS r ON g.groupId = r.groupId
GO

当前触发器,不正确

CREATE TRIGGER UpdateGroupRoleViewTrigger
    ON GroupRoleView
    INSTEAD OF UPDATE
    AS
BEGIN
    DECLARE @tempTable TABLE
                       (
                           groupId       VARCHAR(50),
                           roleName   VARCHAR(50),
                           oldBanAbility TINYINT,
                           newBanAbility TINYINT
                       )
    INSERT INTO @tempTable (groupId, roleName, oldBanAbility, newBanAbility)
    SELECT g.groupId, deleted.roleName, deleted.canBan, inserted.canBan
    FROM deleted
             INNER JOIN inserted ON deleted.groupName = inserted.groupName
             LEFT JOIN [Group] as g on deleted.groupName = g.groupName
    SELECT * FROM @tempTable
    IF (UPDATE(roleName) OR UPDATE(groupName))
        THROW 50003, 'You cannot move role between groups or change role name. Delete existing role and create a new one instead of this.', 1
    IF (UPDATE(canBan))
        BEGIN
            --todo
            UPDATE Role
            SET canBan = (SELECT TOP 1 newBanAbility FROM @tempTable)
            WHERE groupId IN (SELECT groupId FROM @tempTable)
              AND roleName IN (SELECT roleName FROM @tempTable)
            SELECT * FROM deleted
 
        end
end
GO

【问题讨论】:

  • “我当前的解决方案是错误的”和“不正确”并没有给我们太多的帮助。你能再模糊一点吗?除以零?服务器重启?没发生什么事?另外:如果在update 语句中写入列,则update() function 返回true,无论值是否被更改,例如update ... set roleName = roleName ...;.
  • CREATE TRIGGER UpdateGroupRoleViewTrigger ON GroupRoleViewForTrigger INSTEAD OF UPDATE?可能是错字?

标签: sql sql-server database tsql


【解决方案1】:

从外观上看,您已经陷入了假设触发器表中只有一行的classic trap
另请注意,触发器忽略 BEGIN \ END 以标记触发器的结束,因此您的批处理中之后的任何代码仍然包括在内。使用 GO 结束触发器。
此外,如 cmets 中所述,您不会检查值是否已更改。
在已删除/插入的查询中,您还缺少针对 roleName 的连接检查。

您的UPDATE 语句也没有正确进行连接,在这种情况下您根本不需要临时表。

这是正确的版本:

CREATE TRIGGER UpdateGroupRoleViewTrigger
    ON GroupRoleView
    INSTEAD OF UPDATE
    AS

    IF (NOT EXISTS (SELECT 1 FROM inserted))    -- early bail-out
        RETURN;
    SET NOCOUNT ON;    -- prevent confusion when checking rows updated in client

    IF (EXISTS (
        SELECT roleName, groupName FROM inserted
        EXCEPT
        SELECT roleName, groupName FROM deleted))
        THROW 50003, 'You cannot move role between groups or change role name. Delete existing role and create a new one instead of this.', 1;


    IF (UPDATE(canBan))
    BEGIN
        --todo
        UPDATE r
        SET canBan = i.canBan
        FROM deleted d
        -- added rolename equi-join as that appears to be correct from the schema
        INNER JOIN inserted i ON d.groupName = i.groupName AND d.roleName = i.roleName
            AND d.canBan <> i.canBan
        INNER JOIN Role r ON r.groupName = i.groupName AND r.roleName = i.roleName
    END;

GO

但是...

SQL Server 实际上支持updatable views,所以这里实际上根本不需要触发器。您应该能够直接更新视图,只要所有列都在一个表中。

【讨论】:

  • 旁白:我建议使用if not exists ( select 42 from inserted )(或deleted)而不是if @@RowCount = 0 进行“纾困”检查。 @@RowCount 是“脆弱的”,即很容易添加一个语句来更改触发器中的值而没有意识到其含义,可能有其他触发器在此触发器之前执行,merge 以您可能没想到的方式设置值, ... .在大多数情况下,不需要检查,即轻量级触发器和影响零行的语句相对较少。
  • 我认为@@ROWCOUNT 更快但不确定。我不相信其他触发器会影响@@ROWCOUNT,我可能是错的。像这样使用@@ROWCOUNT = 0 编辑触发器的任何人都会失去我来自的工作。 MERGE: 是的,这意味着你不会退出,但谁在乎呢,剩下的仍然是正确的代码。取决于是否有人真的在桌子上使用MERGE
  • @HABO 只是复制了,不,@@ROWCOUNT 将始终是外部 DML 的值。另一个触发器根本不会影响它。顺便问一下,你有什么理由不给我的答案投赞成票或反对票吗?
  • 我已经学会了信任文档,至少在我能做到的范围内。测试很好,但不是保证。在我的老式笔记本电脑上进行快速测试,@@RowCount 大约为 2.2μs,exists 大约为 3.5μs。这是我愿意为我认为更清晰、更健壮的代码付出的代价。 (已经完成工业自动化的人讨厌在一勺钢水周围穿梭时犯错。)一个“错误”,如某人在每个触发器的开头添加或移动set nocount on; 都会导致所有触发器提前退出它将@@RowCount 设置为0
  • 您的答案重复了IF (UPDATE(roleName) OR UPDATE(groupName)) 中的 OP 错误,它不检查更改的值,仅检查写入的列。另外:微软花了很长时间来更正文档以解释为什么select 30 / 3 / 5; 有效而select 30 / -3 / 5; 无效。两者都适用于我尝试过的所有其他 SQL 数据库,我很确定 M$ 违反了 ISO SQL 标准。
【解决方案2】:

嗯,我刚想了个办法,想把它全部写在一张纸上,这对我有帮助。明天会检查它(通过向老师演示),但现在,我认为这个带有连接的 UPDATE 语句是正确的

@charsieface 的回答也对我有所帮助,除了此刻,该表 Role 没有字段 groupName em>,但只有 groupId

UPDATE Role
set canBan = i.canBan
FROM deleted d
INNER JOIN inserted i ON d.groupName = i.groupName AND d.roleName = i.roleName
    AND d.canBan <> i.canBan
INNER JOIN [Group] g ON g.groupName = i.groupName
INNER JOIN Role r on g.groupId = r.groupId AND i.roleName = r.roleName

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-12
    • 2012-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多