可以替换此光标

时间:2014-01-13 14:00:09

标签: sql stored-procedures cursor sql-server-2012

我目前在我的sql server程序中使用游标。想知道是否有更好的方法来替换它。过程是

  1. 客户付了一些钱,我在付款表中为它创建了一个条目。
  2. 我启动一个游标,从PAYMENT TABLE
  3. 中选择具有可用余额的该客户的所有付款
  4. 然后我开始一个内部游标,从BILL TABLE
  5. 获取该客户的所有尚未支付的账单
  6. 我付清每笔账单,直到当前的付款用尽,然后重复这个过程
  7. 如何在步骤2和3中更有效地删除游标。另外,使用游标意味着PAYMENT和BILL表在程序运行之前一直处于锁定状态?

    的Tx

1 个答案:

答案 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))

我假设billspayments表有一个名为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中显示的方式更简单地完成 - 如果在第二个范围结束之前第一个范围开始,则两个范围重叠第二个范围在第一个范围结束之前启动

唯一棘手的事情是决定是否要处理两个相邻的范围(第一个的结束值完全等于第二个的开始,反之亦然)但是这只会导致决定是否要在比较中使用<<=

在这种情况下,我们不关心付款是否完全支付了之前的帐单,因此我们使用<来避免将此类情况视为重叠。