【问题标题】:Why is the INSERTED table of an INSTEAD OF UPDATE trigger empty?为什么 INSTEAD OF UPDATE 触发器的 INSERTED 表为空?
【发布时间】:2010-12-02 11:50:36
【问题描述】:

计划:使用INSTEAD OF INSERT 触发器将失败的插入重定向到“待处理”表。这些行保留在“待处理”表中,直到在另一个表中插入一些附加信息;当此新信息可用时,待处理的行将移至其原始目的地。

背景:交易记录与控股有关。更新交易的服务可以有尚未在数据库中的信息,例如尚未插入的持仓交易(请不要专注于系统那部分的“为什么”,我可以)不要改变它)。

问题:INSTEAD OF INSERT 触发器工作,但我在使用 INSTEAD OF UPDATE 触发器时遇到问题。当应用UPDATE 但要更新的行在“待处理”表中时,触发器中的INSERTED 表为空,因此我无法更新“待处理”表。这是(简化的)DDL:

CREATE TABLE [Holding] (
    [HoldingID] INTEGER NOT NULL,
    [InstrumentID] INTEGER,
    CONSTRAINT [PK_Holding] PRIMARY KEY ([HoldingID])
)
GO
CREATE TABLE [Trade] (
    [TradeID] INTEGER IDENTITY(1,1) NOT NULL,
    [HoldingID] INTEGER NOT NULL,
    [BuySell] CHAR(1) NOT NULL,
    CONSTRAINT [PK_TradeSummary] PRIMARY KEY ([TradeID])
)
GO
ALTER TABLE [Trade] ADD CONSTRAINT [CC_Trade_BuySell] 
    CHECK (BuySell = 'B' or BuySell = 'S')
GO
ALTER TABLE [Trade] ADD CONSTRAINT [Holding_Trade] 
    FOREIGN KEY ([HoldingID]) REFERENCES [Holding] ([HoldingID])
GO
CREATE TABLE [TradePending] (
    [TradeID] INTEGER IDENTITY(1,1) NOT NULL,
    [HoldingID] INTEGER NOT NULL,
    [BuySell] CHAR(1) NOT NULL,
    CONSTRAINT [PK_TradePending] PRIMARY KEY ([TradeID])
)
GO
ALTER TABLE [TradePending] ADD CONSTRAINT [CC_TradePending_BuySell] 
    CHECK (BuySell = 'B' or BuySell = 'S')
GO
-- The INSERT trigger works, when the referenced holding does not exist the row is redirected to the TradePending table.
CREATE TRIGGER [Trg_Trade_Insert]
ON [Trade]
INSTEAD OF INSERT
AS
IF NOT EXISTS (SELECT 1 
    FROM inserted i INNER JOIN Holding h
    ON i.HoldingID = h.HoldingID)
BEGIN
    INSERT TradePending(HoldingID, BuySell) SELECT HoldingID, BuySell FROM inserted
END
ELSE
BEGIN
    INSERT Trade(HoldingID, BuySell) SELECT HoldingID, BuySell FROM inserted
END
GO

执行UPDATE 的触发器在Trade 表中存在行时起作用,但在行不存在时不起作用,INSERTED 虚拟表为空。我在触发器中添加了一些 PRINT 语句,以尝试查看发生了什么。

CREATE TRIGGER [dbo].[Trg_Trade_Update]
ON [dbo].[Trade]
INSTEAD OF UPDATE
AS

DECLARE @s char(1)
DECLARE @h int

IF NOT EXISTS (SELECT 1 
    FROM inserted i INNER JOIN Trade t
    ON i.HoldingID = t.HoldingID)
BEGIN
    PRINT 'Update TradePending'

SET @h = COALESCE((SELECT i.HoldingID
    FROM TradeSummaryPending t INNER JOIN inserted i
        ON t.HoldingID = i.HoldingID), 0)
SET @a = COALESCE((SELECT i.BuySell
    FROM TradeSummaryPending t INNER JOIN inserted i
        ON t.HoldingID = i.HoldingID), 'N')
    PRINT 'h=' + CAST(@h AS varchar(1)) + ' s=' + @s

    UPDATE TradePending 
    SET BuySell = i.BuySell
    FROM Trade t INNER JOIN inserted i
        ON t.HoldingID = i.HoldingID
END
ELSE
BEGIN
    PRINT 'Update Trade'    
    SET @h = (SELECT i.HoldingID
        FROM Trade t INNER JOIN inserted i
            ON t.HoldingID = i.HoldingID)
    SET @s = (SELECT i.BuySell
        FROM Trade t INNER JOIN inserted i
            ON t.HoldingID = i.HoldingID)
    PRINT 'h=' + CAST(@h AS varchar(1)) + ' s=' + @s

    UPDATE Trade    
    SET BuySell = i.BuySell
    FROM Trade t INNER JOIN inserted i
        ON t.HoldingID = i.HoldingID
END

以下是一些用于测试的示例数据:

-- Create a Holding and a Trade, this will be inserted as normal.
INSERT Holding VALUES(1,100)
INSERT TradeSummary VALUES(1,'B')

-- Create a Trade where the Holding does not exists,
-- row redirected to TradePending table.
INSERT TradeSummary values(2,'S')

-- Update the first trade to be a Buy, updates the `Trade` table
UPDATE Trade SET BuySell = 'S' WHERE HoldingID = 1

执行更新的输出:

Update Trade
h=1 s=S

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

现在更新仅存在于 TradePending 表中的行:

UPDATE Trade SET BuySell = 'B' WHERE HoldingID = 2

这会导致以下输出:

Update TradePending
h=0 s=N

(0 row(s) affected)
(0 row(s) affected)

INSERTED 表现在似乎包含行,尽管这是一个 INSTEAD OF 触发器并且应该在 SQL 应用于表之前执行。

谁能解释为什么INSERTED 表是空的?我确信解决方案将是微不足道的,但我似乎无法让它发挥作用。

【问题讨论】:

    标签: sql sql-server sql-server-2008 triggers


    【解决方案1】:

    当然,当您更新 表中不存在的行时,INSERTED 伪表中不存在这些行以开始:您在 Trade 上发出 UPDATE 语句以获取行在TradePending

    此外,您的 INSTEAD OF INSERT 触发器已损坏。它仅适用于单行插入,即使对于那些在并发下会失败的插入。使用基于集合的 MERGE。

    最终,您正在围绕与应用程序的功能脱节的数据模型设计一个 hack。到目前为止,创建 INSTEAD OF 触发器以完全改变遗留代码使用的表的形状才有效,您遇到的这个问题只是未来的众多问题之一。最终,您的客户端代码必须插入/更新/删除正确的表。

    作为一种解决方法,您可以尝试将所有数据移动到一个包含 Trade 和 TradePending 并使用状态列来区分两者的表中,将旧的 Trade 和 TradePending 表显示为 视图 并使用触发器捕获视图上的 DML 以将它们重定向到正确的表。不确定是否可行,我现在无法测试。

    更新:

    这是一个如何使用可更新视图的示例:

    CREATE TABLE [Holding] (
        [HoldingID] INTEGER NOT NULL,
        [InstrumentID] INTEGER,
        CONSTRAINT [PK_Holding] PRIMARY KEY ([HoldingID])
    )
    GO
    
    CREATE TABLE [TradeStorage] (
        [TradeID] INTEGER IDENTITY(1,1) NOT NULL,
        [HoldingID] INTEGER NOT NULL,
        [BuySell] CHAR(1) NOT NULL,
        CONSTRAINT [PK_TradeSummary] PRIMARY KEY ([TradeID])
        , CONSTRAINT [CC_Trade_BuySell] CHECK (BuySell IN ('B','S'))
        )
    GO
    
    create view Trade
    with schemabinding
    as
    select TradeID, HoldingID, BuySell
    from dbo.TradeStorage
    where exists (
        select HoldingID from dbo.Holding
        where Holding.HoldingID = TradeStorage.HoldingID);
    go
    
    create view TradePending
    with schemabinding
    as
    select TradeID, HoldingID, BuySell
    from dbo.TradeStorage
    where not exists (
        select HoldingID from dbo.Holding
        where HoldingID = TradeStorage.HoldingID);
    go  
    
    -- Create a Holding and a Trade, this will be inserted as normal.
    INSERT Holding VALUES(1,100)
    INSERT Trade VALUES(1,'B')
    
    -- Create a Trade where the Holding does not exists,
    -- row redirected to TradePending table.
    INSERT Trade values(2,'B')
    go
    
    select * from Trade;
    select * from TradePending;
    go
    
    -- Update the first trade to be a Buy, updates the `Trade` table
    UPDATE Trade SET BuySell = 'S' WHERE HoldingID = 1
    go
    
    -- Insert a holding with ID 2, 
    -- this will automatically move the pending trade to Trade
    INSERT Holding VALUES(2,100)
    
    select * from Trade;
    select * from TradePending;
    go
    
    UPDATE Trade SET BuySell = 'S' WHERE HoldingID = 2
    go
    
    select * from Trade;
    select * from TradePending;
    go
    

    请注意,对于 TradePending 中的记录,仍然无法更新 Trade。没有触发器、视图或类似机制可以做到这一点。

    【讨论】:

    • 感谢您的分析。我想我的问题中有一个没有被问到的部分是“这是一个疯狂的想法吗?” :) 当您说:“您对 TradePending 中的行发出 UPDATE 语句”时,目的是让 TradePending 表对应用程序隐藏。我认为(可能是错误的)我可以拦截 UPDATE 语句并将更改应用到 TradePending 表。
    • 再次感谢您的帮助和更新的答案。如果无法更新待处理交易,那么我将不得不重新考虑解决方案。我试图避免对写入数据库的服务进行重大更改。
    【解决方案2】:

    我还没来得及运行这个,但你确定插入的表是空的吗? (您总是加入其他表,因此这些表中缺少记录可能会导致该行在您的结果集中被抑制。)删除的呢?对于更新,您应该有一个插入集和一个删除集。

    【讨论】:

    • 我根据 IF EXISTS 检查的结果加入 Trade 或 TradePending 表,因为我的理解是您必须加入 INSERTED 表才能访问它(或者我认为是错误的?)为了测试你的理论,我打印了“SELECT COUNT(*) FROM inserted”,count = 0。与删除相同,count = 0。
    • 好的,我错了需要加入 INSERTED 才能访问它。我将 UPDATE 更改为 TradePending 表:“UPDATE TradePending SET BuySell = (SELECT BuySell FROM inserted WHERE inserted.HoldingID = TradePending.HoldingID) 解析器对此感到满意,但运行时出现错误:“无法插入值 NULL 到列 'BuySell'"
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多