由 CROSS APPLY 触发时的顺序 SQL 插入
Posted
技术标签:
【中文标题】由 CROSS APPLY 触发时的顺序 SQL 插入【英文标题】:Sequential SQL inserts when triggered by CROSS APPLY 【发布时间】:2014-02-19 22:01:47 【问题描述】:这个过程有几个步骤,反映在数据库的各个表中:
Production --> UPDATE
使用类似
UPDATE STOR SET
STOR.BLOC1 = T.BLOC1,
STOR.BLOC2 = T.BLOC2,
STOR.BLOC3 = T.BLOC3,
STOR.PRODUCTION = T.PROD,
STOR.DELTA = T.DELTA
FROM BLDG B INNER JOIN STOR S
ON S.B_ID = B.B_ID
CROSS APPLY dbo.INVENTORIZE(B.B_ID) AS T;
上面提供了一个日志表,带有TRIGGER
,如下所示:
CREATE TRIGGER trgrCYCLE
ON STOR
FOR UPDATE
AS
INSERT INTO dbo.INVT
(TS, BLDG, PROD, ACT, VAL)
SELECT CURRENT_TIMESTAMP, B_ID, PRODUCTION,
CASE WHEN DELTA < 0 THEN 'SELL' ELSE 'BUY' END,
DELTA
FROM inserted WHERE COALESCE(DELTA,0) <> 0
最后,每次更新都应该 INSERT
一行到我添加到上面 TRIGGER 的财务表中:
INSERT INTO dbo.FINS
(COMPANY, TS, COST2, BAL)
SELECT CORP, CURRENT_TIMESTAMP, COST,
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)- COST)
FROM inserted WHERE COALESCE(COST,0) <> 0
问题出在这一行:
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)- COST)
用于计算帐户的最新余额。但是因为CROSS APPLY
将所有INSERTS
视为一个批次,所以计算是根据最后一条记录完成的,我得到的余额数字不正确。示例:
COST BALANCE
----------------
1,000 <-- initial balance
-150 850
-220 780 <-- should be 630
解决这个问题的方法是什么? FINS
表上的触发器,而不是用于余额计算?
【问题讨论】:
将余额记录到表中而不是即时计算通常是一个糟糕的设计... 【参考方案1】:了解查询中的现有逻辑
UPDATE
语句只会为满足连接条件的集合或批次触发一次trigger
,插入的语句将包含所有正在更新的记录。这是因为 BATCH 处理不是因为CROSS APPLY
,而是因为UPDATE
。
在你的这个查询中
SELECT CORP, CURRENT_TIMESTAMP, COST,
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)- COST)
FROM inserted WHERE COALESCE(COST,0) <> 0
对于来自外部查询的每个 CORP,将返回相同的 BAL。
(SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)
话虽如此,每次 CORP = 'XYZ' 时,您的内部查询将被替换为 1000(您在示例中使用的值)
SELECT CORP, CURRENT_TIMESTAMP, COST, (1000- COST)
FROM inserted WHERE COALESCE(COST,0) <> 0
现在您的插入语句包含了所有正在插入的记录。因此,每条记录的成本将减去 1000。因此您会得到意想不到的结果。
建议的解决方案
据我了解,您想计算一些累积频率之类的东西。或上次运行总计
问题陈述的数据准备。用我的虚拟数据给你一个想法。
--Sort data based on timestamp in desc order
SELECT PK_LoginId AS Bal, FK_RoleId AS Cost, AddedDate AS TS
, ROW_NUMBER() OVER (ORDER BY AddedDate DESC) AS Rno
INTO ##tmp
FROM dbo.M_Login WHERE AddedDate IS NOT NULL
--Check how data looks
SELECT Bal, Cost, Rno, TS FROM ##tmp
--Considering ##tmp as your inserted table,
--I just added Row_Number to apply Top 1 Order by desc logic
+-----+------+-----+-------------------------+
| Bal | Cost | Rno | TS |
+-----+------+-----+-------------------------+
| 172 | 10 | 1 | 2012-12-05 08:16:28.767 |
| 171 | 10 | 2 | 2012-12-04 14:36:36.483 |
| 169 | 12 | 3 | 2012-12-04 14:34:36.173 |
| 168 | 12 | 4 | 2012-12-04 14:33:37.127 |
| 167 | 10 | 5 | 2012-12-04 14:31:21.593 |
| 166 | 15 | 6 | 2012-12-04 14:30:36.360 |
+-----+------+-----+-------------------------+
从上次运行余额中减去成本的替代逻辑。
--Start a recursive query to subtract balance based on cost
;WITH cte(Bal, Cost, Rno)
AS
(
SELECT t.Bal, 0, t.Rno FROM ##tmp t WHERE t.Rno = 1
UNION ALL
SELECT c.Bal - t.Cost, t.Cost, t.Rno FROM ##tmp t
INNER JOIN cte c ON t.RNo - 1 = c.Rno
)
SELECT * INTO ##Fin FROM cte;
SELECT * FROM ##Fin
输出
+-----+------+-----+
| Bal | Cost | Rno |
+-----+------+-----+
| 172 | 0 | 1 |
| 162 | 10 | 2 |
| 150 | 12 | 3 |
| 138 | 12 | 4 |
| 128 | 10 | 5 |
| 113 | 15 | 6 |
+-----+------+-----+
您必须在您的专栏中发布一点推文才能将此功能纳入您的触发器。
【讨论】:
‘内部查询将只执行一次’——这是一个不准确的说法。内部查询与外部查询相关(CORP
是来自inserted
的列,COMPANY
来自FINS
)。但是对于相同的CORP
值,结果当然是相同的。不过,OP 已经知道了。
@AndriyM:这很好。我没有观察到相关性。纠正它。顺便说一句,什么是 OP?
是的,我评论中的“OP”是指“原始海报”。我还看到了用于表示“原始帖子”的缩写。 (不过,我自己从来没有用过这个意思。)【参考方案2】:
我认为你可以尝试在鳍上触发。
您可以使用 IDENT_CURRENT('Table')) 从表中获取最后一个主键并进行选择。
我觉得比“select top 1”好。
要获取最后一个余额值,请设置一个变量 last_bal = select bal from FINS where primary_key = Ident_Current("FINS")
【讨论】:
我试了一下,但我想我弄错了。你能告诉我触发器的样子吗?【参考方案3】:好吧
first sql 是一个游戏,它与组一起工作,或者更确切地说是“设置”,所以你总是要考虑这一点。
如果你使用一个简单的项目是正确的,它可能是更好的方法
declare @myinsert table(id int identity(1,1), company VArchar(35), ts datetime, cost2 smallmoney, bal smallmoney)
insert into @myinsert(company,ts, cost2, bal)
SELECT CORP, CURRENT_TIMESTAMP, COST,
FROM inserted WHERE COALESCE(COST,0) <> 0
declare @current int
select @current = min(id) from @myinsert
while exists(select * from @myinsert where id = @current)
begin
INSERT INTO dbo.FINS
(COMPANY, TS, COST2, BAL)
SELECT COMPANY, CURRENT_TIMESTAMP, COST,
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = my.COMPANY ORDER BY TS DESC)- COST)
from @myinsert my where id = @current
select @current = min(id) from @myinsert where id > @current
end
【讨论】:
【参考方案4】:我没有给你确切的查询。暂时忘记触发器。因为你无法测试你的查询。 我建议使用输出子句。这至少可以帮助您构建正确的查询并对其进行测试。 此查询运行正常,(如果您可以使用合并,那就最好了)。
Declare @t table
(
BLOC1,BLOC2,BLOC3 ,PRODUCTION ,DELTA --whatever column is require here
)
UPDATE STOR SET
STOR.BLOC1 = T.BLOC1,
STOR.BLOC2 = T.BLOC2,
STOR.BLOC3 = T.BLOC3,
STOR.PRODUCTION = T.PROD,
STOR.DELTA = T.DELTA
Output inserted.BLOC1 ,inserted.BLOC2, and so on into @t
FROM BLDG B INNER JOIN STOR S
ON S.B_ID = B.B_ID
CROSS APPLY dbo.INVENTORIZE(B.B_ID) AS T;
现在你已经在表变量@t中插入了值
SELECT CORP, CURRENT_TIMESTAMP, COST,
BAL,Row_Number() over(partition by company order by TS desc) RN
FROM @t inner join FINS on COMPANY = CORP
WHERE COALESCE(COST,0) <> 0
在此处验证此查询。考虑稍后优化或触发。 我想我给出了很好的建议。我想减法不是问题。我告诉将所有内容放在输出子句中并分析查询并对其进行测试。
您也可以在触发器中使用 CTE,但您将如何测试它。
;With CTE as
(
SELECT CORP, CURRENT_TIMESTAMP, COST,BAL
ROW_NUMBER()over(ORDER BY TS DESC )rn
FROM inserted
inner join FINS on COMPANY = CORP
WHERE COALESCE(COST,0) <> 0
)
select * from CTE --check this what you are getting
【讨论】:
我在这个查询中没有看到减法逻辑 我的想法是首先使用输出使查询正确。如果查询正确,那么减法不是问题。首先让@Greener 尝试我的方法。如果问题仍然没有解决,然后抛出一些样本sql fiddle 中的数据。 恕我直言,您也应该尝试研究减法逻辑。因为它是一段棘手的代码。他能够非常接近该部分的其余部分。【参考方案5】:类似的东西,不完整。
CREATE TRIGGER trgrCYCLE
ON STOR
FOR UPDATE
AS
begin
declare @last_bal int
declare @company varchar(50)
declare @ts --type
declare @cost int
declare @bal --type
--etc whatever you need
select @company = company, @ts= ts , @cost = cost , @bal = bal from INSERTED
--others selects and sets
set @last_bal = select bal from dbo.FINS where you_primary_key = IDENT_CURRENT('FINS'))
set @last_bal = @last_bal - @cost
Insert INTO FINS (company, ts, cost2, bal) VALUES (@company, @ts, @cost, @last_bal) where --your conditions
end
【讨论】:
【参考方案6】:如果,类似于@Shantanu 的方法,您可以将序列与插入的、与触发器关联的虚拟表关联起来,您可以通过减去当前记录之前的所有 COST 来做到这一点。
这可以通过向 STOR 添加 rowversion 来实现,每次删除都会自动更新。
然后代替:
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)- COST)
from inserted ...
制作 rowversion RV,并且:
(SELECT SUM(X.B) FROM
(SELECT TOP 1 BAL B
FROM FINS
WHERE COMPANY = CORP
ORDER BY TS DESC
UNION
SELECT -COST B
FROM inserted ii
WHERE ii.RV >= i.RV AND ii.CORP = i.CORP
) AS X)
FROM inserted i WHERE COALESCE(COST,0) <> 0
应该做你想做的事。可以想象,您可以使用比 CURRENT_TIMESTAMP 更精细的时间戳来执行此操作,我相信,CURRENT_TIMESTAMP 只会下降到几秒钟,但这需要您在 UPDATE 语句中更新它。 rowversion 可能会导致您的 STOR 插入语句出现问题。
【讨论】:
以上是关于由 CROSS APPLY 触发时的顺序 SQL 插入的主要内容,如果未能解决你的问题,请参考以下文章
SQL 关于apply的两种形式cross apply 和 outer apply
sql MS SQL Cross和Outer Apply Sample
SQL Server outer apply 和 cross apply