【问题标题】:Update data in current row when only one column value changes in that row using trigger使用触发器在该行中只有一个列值更改时更新当前行中的数据
【发布时间】:2020-12-31 02:30:24
【问题描述】:

我有这个用户表,其中有很多列,我想要做的是仅在其中一个列值更改而不是整行时触发触发器。

USE [TempInvestManage]
GO
/****** Object:  Trigger [dbo].[DailyEarning2]    Script Date: 9/13/2020 4:32:21 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[DailyEarning2] ON [dbo].[Users]
after update
as        
    DECLARE @UId INT
    declare @AdCount INT

    SELECT @UId = inserted.ID, @AdCount = inserted.AdCount     
    FROM INSERTED;

    with firstCte(Name,UID,PName,AdCount,ClickRate,ReferComission)
    as
    (
        select distinct Users.Name, Users.ID, Packages.PName, Users.AdCount, Packages.ClickRate, Packages.ReferCommission
        from Users
        inner join UserPackages on UserPackages.U_ID = Users.ID
        inner join Packages on Packages.PID = UserPackages.P_ID
        where U_ID = @UId and UserPackages.PackageStatus = 'true' 
    )
    update Users set DailyEarning = ClickRate * @AdCount
    from firstCte
    where ID = @UId and @AdCount != 0;

    update Users set TotalDailyEarning = TotalDailyEarning+DailyEarning, DailyEarning=0
    where ID = @UId and AdCount = 0;

您现在可以忽略连接。我只需要以某种方式检查触发此触发器时adCount 列是否发生更改,然后才需要更新表。否则我希望这个触发器什么都不做。

【问题讨论】:

  • 您的触发器有一个严重的问题:它假定所有更新都只会影响一行。
  • 有了触发器,写一个更新语句,会影响两个不同的用户(例如UPDATE dbo.Users SET AdCount += 1 WHERE ID IN (1,2);),看看触发器是否更新了他们两个的数据。 (答案是否定的。)
  • 如果两个用户尝试同时更新同一行,一个会赢,另一个会丢失更新。这不是我在这里要讨论的问题。刷新链接并阅读“不准备多行更新”部分 - 我更新了示例以与您的场景更相关(当然没有任何广告计数逻辑)。
  • 再来一次,这不是关于两个不同的人同时更新一行,而是关于编写安全代码并超出了这个单一项目的范围,但我放弃了。祝你好运。

标签: sql sql-server tsql triggers


【解决方案1】:

我相信,这是一个触发器,它实现了您想要的逻辑并且正常工作。注意事项:

  1. 无需 CTE,直接更新源表即可。
  2. 使用CASE 表达式将所有更新组合成一个更新。
  3. 以基于集合的方式使用Inserted,以确保您处理多行更新。在这种情况下,WHERE U.ID IN (SELECT ID FROM Inserted) 完成了这项工作。
  4. 为您的表设置别名,使您的查询更加简洁易读。
ALTER TRIGGER [dbo].[DailyEarning2] ON [dbo].[Users]
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    IF UPDATE(AdCount)
    BEGIN
        UPDATE U set
            DailyEarning = CASE WHEN AdCount != 0 THEN ClickRate * AdCount ELSE 0 END
            , TotalDailyEarning = TotalDailyEarning + CASE WHEN AdCount = 0 THEN DailyEarning ELSE 0 END
        FROM Users U
        INNER JOIN UserPackages UP on UP.U_ID = U.ID
        INNER JOIN Packages P on P.PID = UP.P_ID
        WHERE U.ID IN (SELECT ID FROM Inserted)
        AND UserPackages.PackageStatus = 'true';
    END;
END;

注意:您需要仔细检查我的逻辑是否符合您的要求。

【讨论】:

  • 这是否适用于多行更新?顺便说一句,它不能正常工作..我只需要将 dailyEarning 值添加到 TotalDailyEarning 并在 adCount 列值更改为 0 时将 dailyEarning 设置为 0 如果不是那么简单do dailyEarning= ClickRate*AdCount
  • 这条WHERE U.ID IN (SELECT ID FROM Inserted) 行可确保您处理Inserted 的所有行。
  • @Jazz 并注意不需要 CTE。
  • 您能否分享任何有用的链接,或者例如,当子查询返回多于一行时如何处理该代码,因为在这种情况下更新也会失败。另外,如果尝试更新多个表怎么办?cte 不允许
  • @Jazz 提出新问题并提供minimal reproducible example,即样本数据、预期结果和导致问题的查询。
【解决方案2】:

感谢@Dale K,您让它适用于多行这是针对多个更新测试的工作代码

ALTER TRIGGER [dbo].[DailyEarningTest] ON [dbo].[Users]
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    IF UPDATE(AdCount)
    BEGIN
        WITH firstCte([Name], ID, PName, AdCount, ClickRate, ReferComission, DailyEarning, TotalDailyEarning)
        AS (
            SELECT U.[Name], U.ID, P.PName, U.AdCount, P.ClickRate, P.ReferCommission, U.DailyEarning, U.TotalDailyEarning
            FROM Users U
            INNER JOIN UserPackages UP on UP.U_ID = U.ID
            INNER JOIN Packages P on P.PID = UP.P_ID
            WHERE U.ID IN (SELECT ID FROM Inserted)
            AND PackageStatus = 'true'
        )
        UPDATE firstCte SET
            DailyEarning =
            CASE WHEN AdCount != 0 THEN ClickRate * AdCount ELSE 0 END,
            TotalDailyEarning=TotalDailyEarning+
            CASE WHEN AdCount = 0 
            THEN DailyEarning ELSE 0  END 
        ;
    END;
END;

编辑:- 正如@Dale K cte 所说,真的不需要

ALTER TRIGGER [dbo].[DailyEarningTest] ON [dbo].[Users]
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    IF UPDATE(AdCount)
    BEGIN
        UPDATE U set
            DailyEarning = CASE WHEN AdCount != 0 THEN ClickRate * AdCount ELSE 0 END
            , TotalDailyEarning = TotalDailyEarning + CASE WHEN AdCount = 0 THEN U.DailyEarning ELSE 0 END
        FROM Users U
        INNER JOIN UserPackages UP on UP.U_ID = U.ID
        INNER JOIN Packages P on P.PID = UP.P_ID
        WHERE U.ID IN (SELECT ID FROM Inserted)
        AND PackageStatus = 'true';
    END;
END;

【讨论】:

    猜你喜欢
    • 2014-06-16
    • 2017-11-08
    • 2014-01-15
    • 1970-01-01
    • 2017-06-15
    • 2021-06-02
    • 1970-01-01
    • 1970-01-01
    • 2019-05-16
    相关资源
    最近更新 更多