SQL Server 从表变量更新

Posted

技术标签:

【中文标题】SQL Server 从表变量更新【英文标题】:SQL Server Updating from a table variable 【发布时间】:2014-10-06 09:56:20 【问题描述】:

最终编辑 - 已解决 - 解决方案如下

我不敢相信我不知道这一点,但显然问题在于表变量和真实表都具有相同的名称:@CHECKERS 和 dbo.CHECKERS。触发器显然混淆了两人。我通过更改触发器中的插入和更新操作的顺序来发现这一点,以便在从表变量更新之后插入到真实表中。突然,更新开始顺利通过。

当然,我只创建了 dbo.CHECKERS 来调试@CHECKERS,所以很明显存在一些原始问题,这一切都开始了;但无论如何,问题已经解决,触发器工作正常。我在下面发布它,所以如果您愿意,请随时提供任何反馈。我不是专家(显然),并且总是很高兴收到建设性的反馈。

感谢大家的帮助和 cmets。我希望这篇文章可以帮助那里的人避免头痛......

查询 - 解决方案

ALTER TRIGGER CatchChange ON [dbo].[Orders]

-- Last Updated : 07 Oct 2014

AFTER UPDATE
NOT FOR REPLICATION
-- Trigger not fired by agent updates
AS
BEGIN

    IF UPDATE(UpdateRecordDate)
    -- Trigger fired only by user updates that include a timestamp.
    -- Does **NOT** fire from primary stored procedure (dictated by UpdateRecordDate).
    BEGIN

        -- Table variable for storing **multi-row updates**
        -- Using scalar variables will not work! Look it up.
        DECLARE @CHECKERS AS TABLE 
        (   OrderNum INT,
            Old_DCB INT,
            New_DCB INT,
            Old_MC INT,
            New_MC INT,
            Old_P2 NUMERIC(28,3),
            New_P2 NUMERIC(28,3),
            Old_PQ NUMERIC(28,3),
            New_PQ NUMERIC(28,3),
            Old_QT NUMERIC(28,3),
            New_QT NUMERIC(28,3)
        )

        -- Old & new values populated to table variable.
        -- We can add a table in the same format as the variable &
        -- then check what values were passed, if necessary.
        -- The INSERT for this scenario is commented out at the end.
        INSERT INTO @CHECKERS
        SELECT  I.OrderNo,
                D.DriverCB,
                I.DriverCB,
                D.MCode,
                I.MCode,
                D.Price2,
                I.Price2,
                D.PriceQuantity2,
                I.PriceQuantity2,
                D.Quantity1,
                I.Quantity1
        FROM Inserted I
        JOIN Deleted D ON I.Orderno = D.OrderNo
        -- These checks are crucial, as they cut down on reiteration.
        -- Without them, the program would fire the trigger redundantly.
        -- Here we check for changes to the relevant values &
        -- ensure that the update was a new one, not a repetitive one.
        WHERE I.OrderKind = 0
        AND isnull(D.UpdateRecordDate,convert(DATETIME,0)) <> isnull(I.UpdateRecordDate,convert(DATETIME,0))
        AND (D.DriverCB <> I.DriverCB
            OR D.MCode <> I.MCode
            OR D.Price2 <> I.Price2
            OR D.PriceQuantity2 <> I.PriceQuantity2
            OR D.Price3 <> I.Price3
            OR D.PriceQuantity3 <> I.PriceQuantity3
            OR D.Quantity1 <> I.Quantity1)

        -- Case conditions are preferable in this instance to IF conditions.
        -- All relevant values are checked here against dictating conditions &
        -- updated accordingly.
        UPDATE O
        SET     
        O.DriverCB =        CASE
                            WHEN C.New_DCB = 0
                            THEN NULL
                            ELSE O.DriverCB
                            END,
                            <...>,
        O.Quantity4 =       CASE 
                            WHEN C.New_QT > 0
                            THEN O.Quantity1
                            ELSE O.Quantity4
                            END 
        FROM Orders O
        INNER JOIN @CHECKERS C ON O.OrderNo = C.OrderNum

        -- This is the INSERT which populates the debug table, if you want.
        -- Naturally, the name here must match the name of the created table!
        -- And yes, DD, you need to create the ACTUAL TABLE outside of this trigger. :)
/*      INSERT INTO CheckMe
        SELECT  C.ID,
        C.OrderNum,
        C.Old_DriverCodeBizua,
        C.New_DriverCodeBizua,
        C.Old_MaslulCode1,
        C.New_MaslulCode1,
        C.Old_Price2,
        C.New_Price2,
        C.Old_PriceQuntity2,
        C.New_PriceQuntity2,
        C.Old_Quntity,
        C.New_Quntity
        FROM @CHECKERS C    */
    END
END
GO

结束查询 - 解决方案

--------开始原帖--------

我有一个客户需要我修改一堆执行各种功能的旧触发器。一般来说,我不赞成这一点,并认为他们的程序应该更新 - 但无论如何。

存在三个触发器,我只需要将它们组合成一个触发器,它工作得更顺畅一些,并且有更多条件。 (目前,完全缺乏条件使得触发器在每次操作时都运行,这看起来非常缓慢。)

简而言之,我创建了一个新触发器,在其中声明了一个表变量(下面的查询)。我这样做是为了让自己直接控制列名,而不是简单地使用 SELECT X INTO #temp。此后,我尝试使用 CASE 检查来更新主表中的各个字段,具体取决于我的表变量中记录的更改。

问题是更新没有做任何事情。我没有收到任何错误,但主表中的值保持不变。为了确保表变量正在获取值并且它们可供使用,我向测试数据库添加了一个新表,并在每次触发触发器时将表变量中当前包含的值插入到新表中,如下所示:

        DECLARE @CHECKERS AS TABLE
        (   OrderNum INT,
            Old_DCB INT,
            New_DCB INT,
            Old_MC INT,
            New_MC INT,
            Old_QT NUMERIC(28,3),
            New_QT NUMERIC(28,3)
        )

        INSERT INTO @CHECKERS
        SELECT  I.OrderNo,
                D.DriverCB,
                I.DriverCB,
                D.MCode,
                I.MCode,
                D.Quantity,
                I.Quantity
        FROM Inserted I
        JOIN Deleted D ON I.OrderNo = D.OrderNo
        WHERE I.OrderKind = 0
        AND (D.DriverCB <> I.DriverCB
            OR D.MCode1 <> I.MCode1
            OR D.Quantity <> I.Quantity)

        INSERT INTO CHECKERS
        SELECT  OrderNum,
            Old_DCB,
            New_DCB,
            Old_MC,
            New_MC,
            Old_QT,
            New_QT
        FROM @CHECKERS
        -- An actual table created to check functionality
        -- Works up until this point

        UPDATE Orders
        SET Quantity2 = CASE 
                WHEN New_QT > 0 THEN New_QT
                ELSE Quantity2
                END
        FROM @CHECKERS C
        WHERE OrderNo = C.OrderNum

当然,实际触发器中还有很多更新,但你懂的。

您知道为什么 Orders 更新没有通过吗?可能是一些小的、愚蠢的、令人尴尬的事情——但我再说一遍,无论如何;)谢谢。

--------结束原帖--------

-- 编辑:

我也尝试过这样编写更新:

        UPDATE O
        SET Quantity2 = CASE 
                WHEN C.New_QT > 0 THEN C.New_QT
                ELSE Quantity2
                END
        FROM @CHECKERS C
        INNER JOIN Orders O on O.OrderNo = C.OrderNum

这种方法给出了相同的结果:没有错误消息(或任何其他消息,就此而言),并且没有更新......

-- 第二次编辑:

如果我使用实际表 CHECKERS 而不是表变量 @CHECKERS,则更新通过:

            UPDATE O
            SET Quantity2 = CASE 
                    WHEN C.New_QT > 0 THEN C.New_QT
                    ELSE Quantity2
                    END
            FROM CHECKERS C -- Actual table, not a table variable!
            INNER JOIN Orders O on O.OrderNo = C.OrderNum

无论 JOIN 中是否存在 Orders,这也有效:

UPDATE Orders
            SET Quantity2 = CASE 
                    WHEN C.New_QT > 0 THEN C.New_QT
                    ELSE Quantity2
                    END
            FROM CHECKERS C -- Actual table, not a table variable!
            WHERE OrderNo = C.OrderNum

现在唯一的问题是,在客户的数据库中,我无法添加真正的表 CHECKERS。我将它添加到我的测试数据库中只是为了澄清这些值是否传递到表变量中!这是我不熟悉的表变量的限制吗?我很难在在线文档中找到任何此类限制...

【问题讨论】:

如果您执行选择而不是更新查询,您是否看到应该更新的行? UPDATE 声明后你看到了什么信息? UPDATE 0 也许?或者可能有什么不同? @DanBracuk 是的,如果我切换到 select 语句,我会看到要更新的行。 @nobodynoone 就是这样 - 我没有收到任何消息。如果我收到一条消息,至少我会知道问题可能是什么……不幸的是,什么都没有。 你的触发器在桌子上Orders吗?它是什么类型的触发器?您能否发布不起作用的实际更新声明,您发布的更新声明会生成一条错误消息The multi-part identifier "O.OrderNo" could not be bound. 【参考方案1】:

您需要在 FROM 语句中添加 Orders 作为 INNER JOIN。 试试这个:

UPDATE O
SET Quantity2 = CASE 
        WHEN New_QT > 0 THEN New_QT
        ELSE Quantity2
        END
FROM @CHECKERS C
INNER JOIN Orders O on O.OrderNo = C.OrderNum

【讨论】:

很高兴您指出了这一点,但不幸的是,这是触发器的原始结构。我已经更新了帖子以表明这一点。即使使用 INNER JOIN(或任何其他 JOIN,甚至包括 FULL OUTER JOIN),更新仍然不会影响 Orders。不过,我更喜欢显式 JOINS 而不是隐式 JOINS,所以感谢您的回复。【参考方案2】:

表变量 (@CHECKERS) 和实际表 (dbo.CHECKERS) 具有相同的名称。触发器在他们之间混淆了。通过更改触发器中的操作顺序,这一点变得显而易见。然后我更改了实际表的名称,一切都开始顺利得多。

我对表变量一无所知,在搜索解决方案的几个小时内,我也没有在网上任何地方找到它。

一般信息:此触发器是在 Microsoft SQL Server 2005 中创建的。也许问题已经解决,或者这可能是侥幸。无论哪种方式,将 dbo.CHECKERS 的名称更改为 dbo.CHECKME 为我解决了这个问题。

【讨论】:

以上是关于SQL Server 从表变量更新的主要内容,如果未能解决你的问题,请参考以下文章

将表列值存储到变量中(SQL Server)

从表变量 MS SQL 中删除

SQL Server基础操作(此随笔仅作为本人学习进度记录六 !--程序块和循环)

SQL Server 函数大全

在试听中如何用SQL选中从表选取所有列

如何在 SQL Server 中的 UPDATE 查询的子查询中引用表变量