【问题标题】:SQL Server trigger interfering with select statementSQL Server 触发器干扰 select 语句
【发布时间】:2018-04-05 01:57:58
【问题描述】:

我们在更新期间触发的表上有一个触发器。我们在前端使用实体框架。因此,我们不想在更新时看到中间结果(触发器的结果)。我的触发器中有 SET NOCOUNT ON:

ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
   ON  [dbo].[MedCMDWorkItems]
   AFTER UPDATE
AS 
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

但是当我们执行更新时,我们首先看到中间结果,然后抛出 Entity Framework。

这里是sql命令:

Declare @UserID int
set @UserID = 26
declare @WorkItemID int
Declare @UserRoles TABLE ( roleid INT ,parentroleid INT ,rolename VARCHAR(100) )  

INSERT INTO @UserRoles  SELECT R.id,R.parentroleid,R.ROLE FROM userroles UR 
INNER JOIN roles R ON UR.roleid = R.id WHERE UR.userid = @UserID

Update MedCMDWorkItems with (UPDLOCK) Set AssignedTo = @UserID, AssignedAt = 
getdate(), LastUpdatedAt = getdate(), LastUpdatedBy = @UserID, @WorkItemID = 
ID 
where ID = (SELECT TOP 1 PR.ID FROM MedCMDWorkItems PR --with (UPDLOCK) 
JOIN TaskTypes TT on PR.TaskTypeID = TT.ID WHERE PR.AssignedTo IS NULL AND 
PR.ClosedAt IS NULL 
AND Exists (SELECT 1 FROM @UserRoles WHERE roleid in (852,772)) AND  
((PR.WorkItemTypeID = (select 29 FROM @UserRoles UR WHERE UR.RoleID = 852) 
AND TT.Task in (SELECT Substring(UR1.rolename, Charindex('|', UR1.rolename) 
+ 1, Len(UR1.rolename)) FROM @UserRoles UR1 WHERE UR1.parentroleid in (852)) 
) OR 
(PR.WorkItemTypeID = (select 28 FROM @UserRoles UR WHERE UR.RoleID = 772) 
AND TT.Task in (SELECT Substring(UR1.rolename, Charindex('|', UR1.rolename) 
+ 1, Len(UR1.rolename)) FROM @UserRoles UR1 WHERE UR1.parentroleid in (772))  
) ) 
ORDER BY (CASE WHEN PR.IsUrgent IS NULL THEN 'False' ELSE PR.IsUrgent END) 
DESC, ( CASE  WHEN PR.Priority IS NULL THEN 9999 ELSE PR.Priority END ), 
PR.CreatedAt)

Select * From MedCMDWorkItems PRI  Where ID = @WorkItemID

这里是触发器:

USE [CombinedWorkflow] 
GO
/****** Object:  Trigger [dbo].[trgForMedCMDWorkItemsUpdate]    Script Date: 
4/3/2018 5:20:22 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON  [dbo].[MedCMDWorkItems]
AFTER UPDATE
AS 
BEGIN
SET NOCOUNT ON;

declare @NewAssignedAt DateTime;
declare @OldAssignedAt DateTime;
declare @NewPulledAt DateTime;
declare @ID int;

select @NewPulledAt = i.PulledAt from inserted i;
select @NewAssignedAt = i.AssignedAt from inserted i;
select @OldAssignedAt = d.AssignedAt from deleted d;
select @ID = i.ID from inserted i;

--If work item is being un-assigned
if(@OldAssignedAt is NOT NULL and @NewAssignedAt is NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems 
Set PulledAt = NULL
Where  ID = @ID
END

--if work item is being assinged
ELSE if(@NewAssignedAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems 
Set FirstAssignedAt = @NewAssignedAt
Where FirstAssignedAt is NULL and ID = @ID

END


-- if work item is being pulled
ELSE if(@NewPulledAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems 
Set AssignedAt = @NewPulledAt
Where AssignedAt is NULL and ID = @ID
SET NOCOUNT ON;
Update MedCMDWorkItems 
Set FirstAssignedAt = @NewPulledAt
Where FirstAssignedAt is NULL and ID = @ID
END


Select 1
END

当我在 SSMS 中执行,触发器在结果下,我得到 2 个结果,第一个表示更新了 1 行,然后第二个表示查询的结果。

如果我禁用触发器并执行命令,我不会得到第一个结果,这是启用触发器时我想要的行为。

如何设置此触发器以使其触发但不会导致其他结果?

谢谢, 萨默尔

【问题讨论】:

  • 中间结果是什么意思?带有嵌入式更新的选择是什么意思?这是没有意义的。您需要在这里为我们提供更多背景信息。
  • 示例数据、示例代码、预期结果?触发器有什么作用?从代码运行什么样的语句?你得到了什么样的结果?
  • 您的触发器不正确,您从insertedtable 中获取一个值到一个变量中,但是inserted 表格可以有不止一行。您需要使用inserteddeleted作为表,而不是作为变量
  • 也不需要一个以上的“set nocount on”,因为触发器中的第一个语句就足够了
  • 删除触发器底部的select 1。它根本没有用,甚至可能是您认为自己得到的intermediate results 的原因

标签: sql sql-server entity-framework-6


【解决方案1】:

您需要将inserteddeleted 用作表格,它们可以容纳多行,因此您永远无法从中填充变量。

相反,你必须像这个例子一样构建它:

你的代码(第一部分)

ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON  [dbo].[MedCMDWorkItems]
AFTER UPDATE AS 
BEGIN
  SET NOCOUNT ON;

  declare @NewAssignedAt DateTime;
  declare @OldAssignedAt DateTime;
  declare @NewPulledAt DateTime;
  declare @ID int;

  select @NewPulledAt = i.PulledAt from inserted i;
  select @NewAssignedAt = i.AssignedAt from inserted i;
  select @OldAssignedAt = d.AssignedAt from deleted d;
  select @ID = i.ID from inserted i;

  --If work item is being un-assigned
  if(@OldAssignedAt is NOT NULL and @NewAssignedAt is NULL)
  BEGIN
    SET NOCOUNT ON;
    Update MedCMDWorkItems 
    Set PulledAt = NULL
    Where  ID = @ID
  END

可以用这样的东西代替(未测试,因为我没有你的数据库)

ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON  [dbo].[MedCMDWorkItems]
AFTER UPDATE AS 
BEGIN
  SET NOCOUNT ON;

  update MedCMDWorkItems
  Set PulledAt = NULL
  where ID in ( select i.Id 
                from   Inserted i
                  left join deleted d on i.Id = d.Id
                where  d.AssignedAt is not null
                and    i.AssignedAt is null
              )  

一旦你得到这个,就不会很难弄清楚如何调整触发器的其余部分

【讨论】:

  • 我对触发器进行了建议的更改:更新 MedCMDWorkItems Set PulledAt = NULL where ID in ( select i.Id from Inserted i left join deleted d on i.Id = d.Id where d.AssignedAt不为空且 i.AssignedAt 为空) .... 更新 MedCMDWorkItems Set FirstAssignedAt = (select i.AssignedAt from Inserted i where i.id = ID and i.AssignedAt is not null) where ID in (select i.Id from在 i.AssignedAt 不为空的地方插入 i)
  • 但更改对结果没有影响
  • 您是否从底部删除了 select 1 ?您是否只在触发器中留下代码进行测试,没有别的?
【解决方案2】:

感谢大家的意见。我弄清楚了这个问题。触发器最后有这个:

... 结束

-- if work item is being pulled
ELSE if(@NewPulledAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems 
Set AssignedAt = @NewPulledAt
Where AssignedAt is NULL and ID = @ID
SET NOCOUNT ON;
Update MedCMDWorkItems 
Set FirstAssignedAt = @NewPulledAt
Where FirstAssignedAt is NULL and ID = @ID
END


Select 1
END

不知道最后一行 (Select 1) 的用途是什么(我没有编写触发器 :)),但它导致显示不需要的结果。

萨默

【讨论】:

  • 如果在表中插入了多行,您的触发器将失败。您确实需要将inserteddeleted视为表而不是变量。它现在可能在仅使用一个更新进行测试时有效,但在有多个更新时会在生产中失败
猜你喜欢
  • 2011-06-15
  • 1970-01-01
  • 2015-12-07
  • 2018-01-19
  • 1970-01-01
  • 1970-01-01
  • 2017-08-19
  • 1970-01-01
  • 2020-09-17
相关资源
最近更新 更多