【问题标题】:Handling Subquery returned more than 1 value in SQL Server AFTER UPDATE TRIGGER处理子查询在 SQL Server 更新触发后返回超过 1 个值
【发布时间】:2015-03-30 11:36:20
【问题描述】:

我有以下原始触发器,对我来说效果很好:http://pastebin.com/raw.php?i=mbsrYvJq

我最近发现,当表(放置此触发器)接收批量更新时,这不起作用。我收到以下错误:

消息 512,级别 16,状态 1,过程 IC_ProductUpdate,第 7 行
子查询返回超过 1 个值。这是不允许的,当 子查询遵循 =、!=、、>= 或当子查询用作 一种表达。该语句已终止。

所以我做了一些挖掘,发现了这个答案:https://stackoverflow.com/a/17059565/2332336

所以,我更新了我的触发器以使用光标,它现在看起来像这样:

ALTER TRIGGER [dbo].[IC_ProductUpdate] 
ON [dbo].[StockItem]
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON
    SET XACT_ABORT ON

    DECLARE @StockItemID INT

    DECLARE cur CURSOR FORWARD_ONLY READ_ONLY LOCAL FOR
        SELECT StockItemID
        FROM INSERTED

    OPEN cur

    FETCH NEXT FROM cur INTO @StockItemID

    WHILE @@FETCH_STATUS = 0 BEGIN

        -- Proceed If This Product Is Syncable
        IF (dbo.IC_CanSyncProduct(@StockItemID) = 1)
        BEGIN

            -- Check If Product Was Synced
            IF ((SELECT COUNT(*) FROM IC_ProductCreateQueue WITH (NOLOCK) WHERE StockItemID = @StockItemID) > 0)
            BEGIN

                -- Check If Any Important Columns Was Updated
                IF (UPDATE(Weight) OR UPDATE(SpareNumber1))
                BEGIN

                    -- Check If There Is A [ProductUpdate] Queue Entry Already Exist For This Product
                    IF ((SELECT COUNT(*) FROM IC_ProductUpdateQueue WITH (NOLOCK) WHERE StockItemID = @StockItemID) > 0)
                    BEGIN

                        -- Reset [ProductUpdate] Queue Entry
                        UPDATE IC_ProductUpdateQueue SET Synced = 0
                        WHERE StockItemID = @StockItemID

                    END
                    ELSE
                    BEGIN

                        -- Insert [ProductUpdate] Queue Entry
                        INSERT INTO IC_ProductUpdateQueue (StockItemID, Synced) VALUES
                        (@StockItemID, 0)

                    END

                END

            END
            ELSE
            BEGIN

                -- Insert [ProductCreate] Queue Entry
                INSERT INTO IC_ProductCreateQueue (StockItemID, Synced) VALUES
                (@StockItemID, 0);

                -- Insert [ProductUpdate] Queue Entry
                INSERT INTO IC_ProductUpdateQueue (StockItemID, Synced) VALUES
                (@StockItemID, 0);

            END

        END

        FETCH NEXT FROM cur INTO @StockItemID

    END

    CLOSE cur
    DEALLOCATE cur

END

当我尝试更新触发器 (F5) 时,它似乎不起作用并给我这个错误:

消息 207,级别 16,状态 1,过程 IC_ProductUpdate,第 12 行
列名“StockItemID”无效。

知道为什么会这样吗?有没有更好的方法来更新我的触发器以处理同时更新的多行?

【问题讨论】:

  • 天哪.....不要在触发器中使用游标!这就是您可以为性能做的最差!将其重写为 基于集合的 操作 - 否则会破坏所有数据库性能。 .....触发器应该总是非常小,快速,精益 - 而光标则不是......
  • 嗨,marc_s,感谢您的提示。您能否提供一个示例或链接来指导如何进行基于集合的操作?
  • 您在此触发器中进行了太多处理。触发器在导致它触发的语句的上下文中执行,并且应该非常精简、快速、灵活。不要不要在触发器中进行大量处理!相反:在触发器中,只需将需要执行的操作记入“命令”表;然后创建一个单独的进程(例如,一个 T-SQL 计划作业)来检查该“命令”表,例如每小时一次,如果需要,进行繁重的处理。这样,您的触发器变得 (a) 更易于编写,并且 (b) 对您的数据库系统的负担要小得多

标签: sql-server tsql triggers


【解决方案1】:

我从问题的第一个链接中看到了您的原始触发器,问题就在这里:

DECLARE @StockItemID INT = (SELECT ItemID FROM INSERTED);

您应该处理多个受影响的行,您可以使用joins 来处理并且不声明任何游标,您不需要上面的声明,例如:

IF ((SELECT COUNT(*)
      FROM IC_ProductCreateQueue 
     WHERE StockItemID = @StockItemID) > 0)

可以写成:

IF ((SELECT COUNT(*)
      FROM IC_ProductCreateQueue ic 
     join inserted on ic.StockItemID = i.StockItemID) > 0)

UPDATE IC_ProductUpdateQueue SET Synced = 0
                    WHERE StockItemID = @StockItemID

可以写成:

UPDATE IC_ProductUpdateQueue 
SET Synced = 0
WHERE StockItemID in (select StokItemID from inserted)

INSERT INTO IC_ProductUpdateQueue (StockItemID, Synced) VALUES
                    (@StockItemID, 0)

可以写成:

INSERT INTO IC_ProductUpdateQueue (StockItemID, Synced) 
select StockItemID,0 from inserted

希望这些对您有所帮助。

【讨论】:

  • 这是否适用于检查特定列是否已更新我在这一行 IF (UPDATE(Weight) OR UPDATE(SpareNumber1)) 上做的?
  • 只比较INSERTEDDELETED而不是使用IF UPDATE(
【解决方案2】:

在我的触发器中,我需要能够在继续之前针对行的 PK 调用 dbo.IC_CanSyncProduct() 函数,并且仅在更新某个列时才起作用(即 IF (UPDATE(Weight) OR UPDATE(SpareNumber1)) 等...)

所以,我采用了临时表解决方案并实现了一个伪 foreach 方法,如下所示:

ALTER TRIGGER [dbo].[IC_ProductUpdate] ON [dbo].[StockItem]
AFTER UPDATE
AS
BEGIN

    SELECT RowNum = ROW_NUMBER() OVER(ORDER BY ItemID) , ItemID
    INTO #ProductUpdates
    FROM INSERTED;

    DECLARE @MaxRownum INT;
    SET @MaxRownum = (SELECT MAX(RowNum) FROM #ProductUpdates);

    DECLARE @Iter INT;
    SET @Iter = (SELECT MIN(RowNum) FROM #ProductUpdates);

    WHILE @Iter <= @MaxRownum
    BEGIN

        -- Get Product Id
        DECLARE @StockItemID INT = (SELECT ItemID FROM #ProductUpdates WHERE RowNum = @Iter);

        -- Proceed If This Product Is Sync-able
        IF (dbo.IC_CanSyncProduct(@StockItemID) = 1)
        BEGIN

            -- Check If Product Was Synced
            IF ((SELECT COUNT(*) FROM IC_ProductCreateQueue WITH (NOLOCK) WHERE StockItemID = @StockItemID) > 0)
            BEGIN

                -- Check If Any Important Columns Was Updated
                IF (UPDATE(Weight) OR UPDATE(SpareNumber1))
                BEGIN

                    -- Check If There Is A [ProductUpdate] Queue Entry Already Exist For This Product
                    IF ((SELECT COUNT(*) FROM IC_ProductUpdateQueue WITH (NOLOCK) WHERE StockItemID = @StockItemID) > 0)
                    BEGIN

                        -- Reset [ProductUpdate] Queue Entry
                        UPDATE IC_ProductUpdateQueue SET Synced = 0
                        WHERE StockItemID = @StockItemID

                    END
                    ELSE
                    BEGIN

                        -- Insert [ProductUpdate] Queue Entry
                        INSERT INTO IC_ProductUpdateQueue (StockItemID, Synced) VALUES
                        (@StockItemID, 0)

                    END

                END

            END
            ELSE
            BEGIN

                -- Insert [ProductCreate] Queue Entry
                INSERT INTO IC_ProductCreateQueue (StockItemID, Synced) VALUES
                (@StockItemID, 0);

                -- Insert [ProductUpdate] Queue Entry
                INSERT INTO IC_ProductUpdateQueue (StockItemID, Synced) VALUES
                (@StockItemID, 0);

            END

        END

        SET @Iter = @Iter + 1;

    END

    DROP TABLE #ProductUpdates;

END

我测试了这样的代码:

UPDATE StockItem set Code = Code where ItemID in (965638, 965650, 965662、965674、965686)

现在它可以工作了(不再有Subquery returned more than 1 value 错误):

(5 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(5 row(s) affected)

这种方法的唯一问题是;检查特定列是否已更新不起作用。

这是个坏主意吗?方法不对?

【讨论】:

    猜你喜欢
    • 2022-01-17
    • 1970-01-01
    • 1970-01-01
    • 2013-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-08
    相关资源
    最近更新 更多