为什么INSTEAD OF UPDATE触发器的INSERTED表空为空?

时间:2010-12-02 11:50:37

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

计划:使用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表是空的?我确信解决方案将是微不足道的,但我似乎无法让它发挥作用。

2 个答案:

答案 0 :(得分:3)

当您更新在表中不存在的行时,INSERTED伪表中的行不存在:您在Trade上发出UPDATE语句对于TradePending中的行!

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

最终,您正在设计一个与应用程序的连接断开的数据模型。创建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。没有触发器,视图或类似机制可以做到这一点。

答案 1 :(得分:0)

我没时间运行它,但你确定插入的表是空的吗? (您总是加入其他表,因此这些表中缺少记录可能会导致在结果集中抑制行。)删除了什么?要进行更新,您应该有一个已插入和已删除的设置。