为什么 INSTEAD OF UPDATE 触发器的 INSERTED 表为空?
计划:使用 INSTEAD OF INSERT
触发器将失败的插入重定向到“待处理”表。这些行保留在“待处理”表中,直到将一些附加信息插入到另一个表中为止;当此新信息可用时,挂起的行将移动到其原始目的地。
背景:记录与控股相关的交易。更新交易的服务可能包含数据库中尚未存在的信息,例如尚未插入的控股交易(请不要关注系统该部分的“原因”,我可以”不要改变这一点)。
问题:INSTEAD OF INSERT
触发器工作正常,但我在使用 INSTEAD OF UPDATE
触发器时遇到问题。当应用 UPDATE
但要更新的行位于“pending”表中时,触发器中的 INSERTED
表为空,因此我无法更新“pending”表。这是(简化的)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
当 Trade
表中存在该行时,执行 UPDATE
的触发器起作用,但当该行不存在时,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
表是空的?我确信解决方案将是微不足道的,但我似乎无法让它发挥作用。
The Plan: To use an INSTEAD OF INSERT
trigger to redirect failed inserts to a 'pending' table. These rows remain in the 'pending' table until some addition information is inserted in another table; when the this new information is available the pending rows are moved to their original destination.
Background: Trades are recorded relating to a Holding. The service updating the Trades can have information which is not yet in the database, such as a trade on a Holding which has not been inserted yet (please don't focus on the 'why' of that part of the system, I can't change that).
Problem: The INSTEAD OF INSERT
trigger working but I'm having trouble with the INSTEAD OF UPDATE
trigger. When an UPDATE
is applied but the rows to be updated are in the 'pending' table the INSERTED
table in the trigger is empty and therefore I cannot update the 'pending' table. Here's the (simplified) 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
The Trigger to do the UPDATE
works when the row exists in the Trade
table but not when the row does not exist, the INSERTED
virtual table is empty. I have added some PRINT
statements to the trigger to try to see what is happening.
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
Here's some sample data for testing:
-- 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
The output from executing the update:
Update Trade
h=1 s=S
(1 row(s) affected)
(1 row(s) affected)
Now update the row which only exists in the TradePending table:
UPDATE Trade SET BuySell = 'B' WHERE HoldingID = 2
Which results in the following output:
Update TradePending
h=0 s=N
(0 row(s) affected)
(0 row(s) affected)
The INSERTED
table appears to contain now rows even though this is an INSTEAD OF
trigger and should be executed before the SQL is applied to the table.
Can anyone explain why the INSERTED
table is empty? I'm sure the solution is going to be something trivial but I just can't seem to get it working.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
当然,当您更新表中不存在的行时,INSERTED 伪表中不存在这些行:您对
Trade
发出 UPDATE 语句对于TradePending
中的行!此外,您的 INSTEAD OF INSERT 触发器已损坏。它仅适用于单行插入,即使对于单行插入,它也会在并发情况下失败。使用基于集合的合并。
最终,您正在围绕与应用程序的功能无关的数据模型设计一种黑客攻击。创建 INSTEAD OF 触发器来完全更改遗留代码使用的表的形状到目前为止仅有效,您遇到的这个问题只是未来的众多问题之一。最终,您的客户端代码必须插入/更新/删除正确的表。
作为解决方法,您可以尝试将所有数据移动到一个同时保存 Trade 和 TradePending 的表中,并使用状态列来区分两者,将旧的 Trade 和 TradePending 表公开为视图 > 并使用触发器捕获视图上的 DML,以将它们重定向到正确的表。不确定是否有效,我现在无法测试它。
更新:
以下是如何与可更新视图配合使用的示例:
请注意,仍然无法更新 TradePending 中的记录的 Trade。没有触发器、视图或类似的机制可以做到这一点。
Of course the rows don't exists in the INSERTED pseudo-table when you update rows that don't exists in the table to start with: you issue UPDATE statement on
Trade
for rows that are inTradePending
!Besides, your INSTEAD OF INSERT trigger is broken. It only works for single row inserts, and even for those it will fail under concurrency. Use a set based MERGE.
Ultimately you are designing a hack around a data model that is disconnected from what the application does. Creating INSTEAD OF triggers to completely change the shape of a table used by legacy code only works so far, this problem you encountered is just one of the many issues down the road. Ultimately, your client code has to insert/update/delete the right table.
As a workaround you could try moving all data into a table that holds both Trade and TradePending and uses a state column to distinguish the two, expose the old Trade and TradePending tables as views and use triggers to capture the DML on the views to redirect them to the proper table. Not sure if would work though, I cannot test it right now.
Update:
Here is an example how this would work with updateable views:
Note that is still no possible to update Trade for records that are in TradePending. No trigger, view or similar mechanism can do such.
我还没有时间运行这个,但是你确定插入的表是空的吗? (您总是连接到其他表,因此这些表中缺少记录可能会导致该行在您的结果集中被抑制。) 已删除的行又如何呢?对于更新,您应该有一个插入集和一个删除集。
I have not had time to run this, but are you sure that the inserted table is empty? (You are always joining to other tables, so the lack of records in those tables may cause the row to be suppressed in your result sets.) What about the deleted? For an update, you should have an inserted and a deleted set.