【问题标题】:Fire SQL Trigger When Column Value Changes列值更改时触发 SQL 触发器
【发布时间】:2021-09-08 20:13:33
【问题描述】:

当“工作状态”为“完成”时,我将日期添加到 SQL 的列中,但我的问题是,当我在软件中再次打开并保存相同的作业时,它会运行触发器并再次更改日期到一个我不想要的新值。

我希望触发器仅在“工作状态”值不是“已完成”时运行。

    GO
/****** Object:  Trigger [dbo].[TRJCD_JOBREQUEST]    Script Date: 06/25/2021 15:49:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER            TRIGGER [dbo].[TRJCD_JOBREQUEST] ON [dbo].[TBL_JOBREQUEST] 
AFTER UPDATE,INSERT

AS

if (Update (workstatus))

begin

DECLARE @Jobcompletiondate datetime 
DECLARE @workstatus VARCHAR(15)
DECLARE @jobid int


select @workstatus = workstatus from inserted
select @jobid = jobid from inserted
select @Jobcompletiondate = GETDATE() 

begin

if @workstatus='Completed'


    update TBL_JOBREQUEST set JobCompDate=@Jobcompletiondate where jobid = @jobid 
end
end

【问题讨论】:

  • 触发器是高度特定于供应商的 - 所以请添加标签以指定您使用的是mysqlpostgresqlsql-serveroracle 还是db2 - 或者完全是别的东西。
  • 您的触发器有一个all too common issue
  • 触发器在执行指定语句时运行,而不是在您想要它运行时运行。您可以使用deletedinserted 来确定特定列的beforeafter 值,并决定在触发器中执行什么操作。请注意,update() function 返回true,无论列的值是否已更改,例如update Foo set Bar = 42 where Bar = 42; 将导致 Update( Bar ) 成为 true
  • IF UPDATE() 不符合您的想法。如果它针对工作状态列中没有实际更改的行运行,那么您有一个有缺陷的应用程序正在以低效的方式更新行。修复两者。如果您的代码必须仅在列更改时运行,则需要比较旧值和新值。
  • 感谢@HABO。我不是 sql 专家。你能编辑我的代码来比较旧的新值吗?我只想在“工作状态”的现有值为“待定”且新值为“已完成”时更新“JobCompDate”列中的日期。我试过这个语句,但它不起作用 [update TBL_JOBREQUEST set JobCompDate=(at)Jobcompletiondate where jobid = (at)jobid and workstatus'Completed']

标签: sql-server tsql triggers


【解决方案1】:

以下是构建触发器的方法。

无需为变量分配任何值,触发器每批次触发一次,并且始终在更新的行的集合上操作。

如果您将状态更新为已完成,您需要检查它当前是否已完成,此外,如果您想保留第一个 JobCompDate,即使状态随后被修改,只需使用 case 表达式仅更新当前所在的列空。

create or alter trigger [dbo].[TRJCD_JOBREQUEST] on [dbo].[TBL_JOBREQUEST] 
after update,insert
as
if @@RowCount=0 return

set nocount on
if Update (workstatus)
begin

    update t set
        t.JobCompDate=case when t.JobCompDate is null then GetDate() else t.JobCompDate end
    from inserted i join TBL_JOBREQUEST t on t.jobid=i.jobid
    where i.workstatus='Completed' 
    and not exists (
      select * from deleted d 
      where d.jobid=i.jobid and d.workstatus=i.workstatus
    )

end

【讨论】:

  • 这对于将deleted 留空的insert 语句如何工作?一个 otter [sic] 加入可能会有所帮助。
  • @HABO 是的,确实感谢您指出这一点-它既是插入又是更新,它不应该是删除时的内部连接-我已修改为使用exists
  • 为什么您选择更新行而不更改JobCompDate 的值(通过使用case 表达式)而不是在where 子句中包含t.JobCompDate is NULL 条件?它会提高性能吗?另外:OP 遵循 DDL 隐藏的标准规则,并没有暗示该列可以为空。
【解决方案2】:

请注意,我没有您的数据集,因此无法测试触发器,但是,根据您在问题中提供的内容,我相信这就是您正在寻找的答案:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER            TRIGGER [dbo].[TRJCD_JOBREQUEST] ON [dbo].[TBL_JOBREQUEST] 
AFTER UPDATE,INSERT

AS

if (Update (workstatus))

begin

DECLARE @Jobcompletiondate datetime 
DECLARE @currentworkstatus VARCHAR(15)
DECLARE @oldworkstatus VARCHAR(15)
DECLARE @jobid int


select @oldworkstatus = workstatus from deleted
select @currentworkstatus = workstatus from inserted
select @jobid = jobid from inserted
select @Jobcompletiondate = GETDATE() 

begin

if @currentworkstatus='Completed' and @oldworkstatus <> 'Completed'


    update TBL_JOBREQUEST set JobCompDate=@Jobcompletiondate where jobid = @jobid 
end
end

您需要检查删除的workstatus 是否不等于Completed,然后才应该触发触发器。

【讨论】:

  • 当单个insertupdate语句影响多行,即inserteddeleted包含多行时,该处理哪一行?
  • 它仅根据作业 ID 更新一行。该软件仅将所有数据插入一行中的列中。
  • select @jobid = jobid from inserted如何从inserted的10行中得到正确的一个值?
  • 此表上的存储过程仅插入 1 个作业 ID,并且仅更新/插入表中的 1 行。
  • @MohammadFerozKhan 你应该为你的现实添加一个检查,例如if ( select Count(*) from inserted ) &gt; 1 begin rollback transaction; throw 50666, N'TRJCD_JOBREQUEST: No more than one row may be processed because it takes a little more effort.', 42; end;,触发。无论是对 SP 的更改还是运行临时查询的用户,您都应该检测到问题并处理它,而不是默默地失败。或者修复代码,使其适用于多行。
猜你喜欢
  • 2016-02-06
  • 1970-01-01
  • 2021-08-04
  • 2014-10-09
  • 1970-01-01
  • 2017-11-08
  • 2012-07-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多