我目前在我的sql server程序中使用游标。想知道是否有更好的方法来替换它。过程是
如何在步骤2和3中更有效地删除游标。另外,使用游标意味着PAYMENT和BILL表在程序运行之前一直处于锁定状态?
的Tx
答案 0 :(得分:1)
这是一种可以完成的方式,包括表格和数据,因为我们不知道你的样子。我在一些地方放了一些叙述,但所有代码都应该作为一个单独的脚本运行。
数据设置:
declare @bills table (billid int, balance decimal(38,4))
declare @payments table (paymentid int, balance decimal(38,4))
insert into @bills (billid, balance) values
(1,0), (2,22.50), (3,12.75), (4,19.20)
insert into @payments (paymentid,balance) values
(1,20.19),(2,5.50),(3,20)
declare @newpayments table (billid int, paymentid int,
paymentamount decimal(38,4))
我假设bills
和payments
表有一个名为balance
的列,它显示了尚未处理的任何金额。或者,您可能需要从几列中计算出来。但是你的问题中没有样本数据意味着我可以构建一个简单的结构: - )
查询填写@newpayments
应从哪些(部分)付款 1 支付账单:
; With unpaidbills as (
select billid,balance,
ROW_NUMBER() OVER (ORDER BY billid) as rn,
SUM(balance) OVER (ORDER BY billid
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) as endbalance,
SUM(balance) OVER (ORDER BY billid
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) - balance as startbalance
from @bills
where balance > 0
), unusedpayments as (
select paymentid,balance,
ROW_NUMBER() OVER (ORDER BY paymentid) as rn,
SUM(balance) OVER (ORDER BY paymentid
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) as endbalance,
SUM(balance) OVER (ORDER BY paymentid
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) - balance as startbalance
from @payments
where balance > 0
), overlaps as (
select
billid,paymentid,
CASE WHEN ub.startbalance < up.startbalance
THEN up.startbalance ELSE ub.startbalance END as overlapstart,
CASE WHEN ub.endbalance > up.endbalance
THEN up.endbalance ELSE ub.endbalance END as overlapend
from
unpaidbills ub
inner join
unusedpayments up
on
ub.startbalance < up.endbalance and
up.startbalance < ub.endbalance
)
insert into @newpayments(billid,paymentid,paymentamount)
select billid,paymentid,overlapend - overlapstart as paymentamount
from overlaps
此时,@newpayments
可用于生成交易历史记录等
然后,最后我们更新原始表格以标记使用的数量:
;With totalpaid as (
select billid,SUM(paymentamount) as payment from @newpayments
group by billid
)
update b
set b.balance = b.balance - tp.payment
from @bills b
inner join
totalpaid tp
on b.billid = tp.billid
;With totalused as (
select paymentid,SUM(paymentamount) as payment from @newpayments
group by paymentid
)
update p
set p.balance = p.balance - tu.payment
from @payments p
inner join
totalused tu
on p.paymentid = tu.paymentid
关键部分是使用SUM()
和window functions来计算欠款(账单)或可用金额(付款)的运行总数,在这两种情况下都使用列(billid或paymentid)确定应该以何种顺序处理这些项目。例如。 unpaidbills
CTE生成如下结果集:
billid balance rn endbalance startbalance
----------- --------- -------------------- ------------- -------------
2 22.5000 1 22.5000 0.0000
3 12.7500 2 35.2500 22.5000
4 19.2000 3 54.4500 35.2500
和unusedpayments
看起来像这样:
paymentid balance rn endbalance startbalance
----------- ---------- -------------------- ------------ -------------
1 20.1900 1 20.1900 0.0000
2 5.5000 2 25.6900 20.1900
3 20.0000 3 45.6900 25.6900
然后我们创建overlaps
CTE,在账单和付款之间找到重叠 2 ,其中(部分)付款可用于满足(部分)账单。重叠的区域是该账单的实际支付金额。
1 不需要ROW_NUMBER()
次来电。在编写此查询的早期阶段,我认为我将使用这些,但结果证明是不必要的。但是删除它们不会缩短到足以允许SO无论如何都停止滚动查询,所以我也可以将它们留在(并且不必编辑显示在较低位置的结果集)
2 许多人试图找到重叠使事情变得荒谬复杂,并处理许多特殊情况以找到所有重叠。这通常可以通过我在overlaps
CTE中显示的方式更简单地完成 - 如果在第二个范围结束之前第一个范围开始,则两个范围重叠 ,和第二个范围在第一个范围结束之前启动。
唯一棘手的事情是决定是否要处理两个相邻的范围(第一个的结束值完全等于第二个的开始,反之亦然)但是这只会导致决定是否要在比较中使用<
或<=
。
在这种情况下,我们不关心付款是否完全支付了之前的帐单,因此我们使用<
来避免将此类情况视为重叠。