SQL 更新 WHERE IN 子查询已停止工作 [重复]
Posted
技术标签:
【中文标题】SQL 更新 WHERE IN 子查询已停止工作 [重复]【英文标题】:SQL Update WHERE IN subquery has stopped working [duplicate] 【发布时间】:2021-04-14 18:38:43 【问题描述】:我有这张桌子。 (还有其他字段,但 Id 和 Lang 在这里很重要。)
CREATE TABLE Request
(
Id int IDENTITY(1,1) NOT NULL,
Lang nvarchar(20) NULL
CONSTRAINT PK_MyTable PRIMARY KEY CLUSTERED (Id ASC)
) ON [PRIMARY]
我有一个用户定义的表类型。
CREATE TYPE dbo.IntList AS TABLE
(
Item int NOT NULL
)
最后,我有一个存储过程,它为我的表类型接收一个变量,以更新所有指定 Id 的 Lang 字段。
CREATE PROCEDURE BulkUpdLang
@ids as IntList READONLY,
@lang as nvarchar(20)
AS
BEGIN
SET NOCOUNT ON;
UPDATE Request
SET Lang = @lang
WHERE Id IN (SELECT Item FROM @ids);
END
这工作正常,但由于某种原因已停止。它现在因错误而爆炸:
子查询返回超过 1 个值。当子查询跟随 =、!=、、>= 或子查询用作表达式时,这是不允许的
假设 MyTable 有 10 个 ID 为 1-10 的项目,这是我尝试过的:
declare @ids IntList;
insert into @ids
select 3 union all
select 4 union all
select 5;
-- This is how I test the sproc
-- EXEC BulkUpdLang @ids, 'en-us';
-- This is just a select and works just fine.
--Select * from Request
-- Where Id in (Select Item From @ids);
-- This WAS working, but now throws the subquery error.
Update Request
Set Lang = 'en-us'
Where Id in (Select Item From @ids);
-- This works as expected. Unfortunately, the Ids need to be a variable.
--Update Request
--Set Lang = 'en-us'
--Where Id in (3, 4, 5);
-- This also throws the subquery error.
--Merge Request as Req
--Using @ids as Ids -- "Using (Select Item From @ids) as Ids" changes nothing
--on Req.Id = Ids.Item
--when matched then
--update set
-- Req.Lang = 'en-us';
-- This also throws the subquery error.
--Update Request
--Set Lang = 'en-us'
--From (Select Item From @ids) Ids
--Where Id = Ids.Item;
让我抓狂的是,这确实有效。我倾向于相信某些 SQL 服务器设置已经改变了它处理子查询的方式。有没有人有任何想法?我快疯了!
这个表上还有一个触发器:
ALTER TRIGGER [dbo].[Request_Status_Audit]
ON [dbo].[Request]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @oldStatus nvarchar(15) = (SELECT [Status] FROM Deleted);
DECLARE @newStatus nvarchar(15) = (SELECT [Status] FROM Inserted);
IF (@oldStatus <> @newStatus) BEGIN
INSERT INTO dbo.Audit_RequestStatus (RequestId, StatusDate, StatusValue)
SELECT Inserted.Id, GETDATE(), Inserted.[Status]
FROM Inserted;
END
-- data retention
DELETE FROM dbo.Audit_RequestStatus
WHERE StatusDate < DATEADD(YEAR, -6, GETDATE());
END
【问题讨论】:
此代码不会产生该错误。我敢打赌有人在桌子上创建了一个错误的触发器。 不要禁用触发器,修复它。 虽然你可以在 proc 中禁用触发器,但不要去那里。我同意@Larnu,你应该修复它(或放弃它)。 您的触发器不是基于集合的。对于超过 1 行的任何更新,这些变量的值是多少? 这是一个不考虑多行指令的 SQL Server 触发器的经典错误。与其将 id 提取到单个变量中,不如使用基于集合的构造来处理所有内容。 【参考方案1】:您的触发器不处理多行插入和更新(也不处理 0 行),正如 by Brent Ozar (The Silent Bug I Find in Most Triggers) 和其他人所记录的那样。
CREATE OR ALTER TRIGGER [dbo].[Request_Status_Audit]
ON [dbo].[Request]
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON;
IF NOT UPDATE([Status]) OR NOT EXISTS
(
SELECT Id, [Status]
FROM inserted
EXCEPT
SELECT Id, [Status]
FROM deleted
)
RETURN ;
INSERT INTO dbo.Audit_RequestStatus (RequestId, StatusDate, StatusValue)
SELECT i.Id, GETDATE(), i.[Status]
FROM (
SELECT Id, [Status]
FROM inserted
EXCEPT
SELECT Id, [Status]
FROM deleted
) i;
-- data retention
DELETE FROM dbo.Audit_RequestStatus
WHERE StatusDate < DATEADD(YEAR, -6, GETDATE());
GO
注意事项:
比较inserted
和deleted
中的必要列以查看是否有更改,还要检查UPDATE
函数(注意UPDATE
只告诉您该列是否存在于查询中,而不是数据是否实际存在已更改),如果可以,请尽早退出。
在您的 INSERT
中再次检查更改的列
我强烈建议将DELETE
转移到代理工作中。
【讨论】:
【参考方案2】:我的问题是一个带有子查询的触发器,我错误地认为总会有一个结果。显然,我陷入了一个经典的陷阱。根据人们提供的 cmets 和链接,我将触发器更新为此,现在一切正常。
INSERT INTO dbo.Audit_RequestStatus (RequestId, StatusDate, StatusValue)
SELECT Inserted.Id, GETDATE(), Inserted.[Status]
FROM Inserted
JOIN Deleted ON Inserted.Id = Deleted.Id
WHERE Inserted.[Status] <> Deleted.[Status];
感谢 Dan Guzman 和 Alejandro 帮助我开发此修复程序。
【讨论】:
这不会对 INSERT 执行任何操作(因为 INSERTED 表中没有记录)。如果这是正确的,触发器应该只为 UPDATE 定义,但目前它也在 INSERT 上触发。此外,如果状态为 NULLable,则此代码无法处理(尽管原始代码也没有)。 像我一样尝试使用基于集合的运算符,例如EXCEPT
,这样可以避免@MatBailie 提到的问题以上是关于SQL 更新 WHERE IN 子查询已停止工作 [重复]的主要内容,如果未能解决你的问题,请参考以下文章
SQL 效率:WHERE IN 子查询与 JOIN 然后 GROUP