2014-01-13 62 views
0

我目前在我的sql服務器程序中使用遊標。想知道是否有更好的方法取代它。過程是這個遊標可以被替換嗎

  1. 客戶支付一些錢,我創建了支付表把新的紀錄。
  2. 我開始其選擇的客戶有一個可用餘額的所有款項光標自繳費表
  3. 然後,我開始是獲取客戶目前仍在未付的所有賬單,比爾表內光標
  4. 我還清每張紙幣,直到目前的付款耗盡,然後重複上述過程

如何刪除光標進行更有效的方式在步驟2和3也並使用遊標意味着支付比爾表保持鎖定,直到程序運行?

的Tx

+1

你絕對可以使用基於集合的方法。如果你需要特定的幫助,你需要共享你的表格結構和現有的代碼。 – JNK

回答

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與費都要從(部分)支付支付:

; 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指找到重疊的賬單和支付其中間(部分的)付款可用於滿足(部分)帳單。重疊區域是該賬單支付的實際金額。


並不真正需要的ROW_NUMBER()電話。在寫這個查詢的早期部分,我想我會用這些,但事實證明這是不必要的。但刪除它們不會縮短足夠的事情來讓SO停止滾動該查詢,因此我不妨將它們留在(並且不必編輯顯示在下面的結果集)

很多試圖找到重疊的人會使事情變得荒謬複雜,並處理許多特殊情況以找到所有重疊。這通常可以在我展示在overlaps CTE的方式更簡單地完成 - 兩個範圍重疊,如果第一個範圍開始第二範圍開始第一個範圍之前結束第二範圍之前結束

要做的唯一棘手的事情是決定是否要處理兩個相鄰的範圍(第一個的結束值完全等於第二個的開始,反之亦然),但這隻會導致決定是否在比較中使用<<=

在這種情況下,我們不在乎付款是否準確支付了以前的帳單,因此我們使用<來避免將這種情況視爲重疊。

+0

謝謝你,很好的回答! – user1763470

相關問題