2012-03-30 1 views
6

私のプログラムでは、このような計算を行うためにクエリに量を渡す必要がありますが、私の場合は行ごとにループして正しい量を差し引いています。効率的な実装方法ではありません。私はここでより良い方法を模索しています。T-SQLでそのような計算ロジックを実行する最善の方法

PS:私のドラフトコードです。何らかの理由で完全なソースコードを投稿できないのは残念です。今、私はコードをより完全かつ合理的にするために再構造化しました。

--- the amount column is just for reference. 

    insert into tbl1 (idx,amount,balance) values (1, 50, 50) 
    insert into tbl1 (idx,amount,balance) values (2, 30, 30) 
    insert into tbl1 (idx,amount,balance) values (3, 20, 20) 
    insert into tbl1 (idx,amount,balance) values (4, 50, 50) 
    insert into tbl1 (idx,amount,balance) values (5, 60, 60) 


declare @total_value_to_deduct int 
declare @cs_index int, @cs_balance int, @deduct_amount int 

set @total_value_to_deduct = 130 

declare csDeduct Cursor for select idx, balance from tbl1 where balance > 0 
open csDeduct fetch next from csDeduct into @cs_index, @cs_balance 

while @@FETCH_STATUS = 0 and @total_value_to_deduct > 0 
begin 

    if @cs_balance >= @total_value_to_deduct 
    set @deduct_amount = @total_value_to_deduct 
    else 
    set @deduct_amount = @cs_balance 

    -- contine deduct row by row if the total_value_to_deduct is not 0 
    set @total_value_to_deduct = @total_value_to_deduct - @deduct_amount 

    update tbl1 set balance = balance - @deduct_amount where idx = @cs_index 
    fetch next from csDeduct into @cs_index, @cs_balance 
end 

close csDeduct 
deallocate csDeduct 

期待される結果:

idx   amount   balance 
1    50    0 
2    30    0 
3    20    0 
4    50    20 
5    60    60 

あなたの助けに感謝しなければならないです。

+2

+1。 (while @total_value_to_deduct <= 0'は意味がありませんが、最初から> 0なので) – Tomalak

+0

minorポイント: 'close'と' deallocate'は間違った方向です。 – Jamiec

+0

申し訳ありませんが、私はすでにそれに応じて変更しました – skywills

答えて

4

リビジョン1:私は

1第三の溶液を添加した)第一の溶液(SQL2005の+; online query

DECLARE @tbl1 TABLE 
(
    idx INT IDENTITY(2,2) PRIMARY KEY, 
    amount INT NOT NULL, 
    balance INT NOT NULL 
); 

INSERT INTO @tbl1 (amount,balance) VALUES (50, 50); 
INSERT INTO @tbl1 (amount,balance) VALUES (30, 30); 
INSERT INTO @tbl1 (amount,balance) VALUES (20, 20); 
INSERT INTO @tbl1 (amount,balance) VALUES (50, 50); 
INSERT INTO @tbl1 (amount,balance) VALUES (60, 60); 


DECLARE @total_value_to_deduct INT; 
SET @total_value_to_deduct = 130; 

WITH CteRowNumber 
AS 
(
    SELECT *, ROW_NUMBER() OVER(ORDER BY idx) AS RowNum 
    FROM @tbl1 a 
), CteRecursive 
AS 
(
    SELECT a.idx, 
      a.amount, 
      a.amount AS running_total, 
      CASE 
       WHEN a.amount <= @total_value_to_deduct THEN 0 
       ELSE a.amount - @total_value_to_deduct 
      END AS new_balance, 
      a.RowNum 
    FROM CteRowNumber a 
    WHERE a.RowNum = 1 
    --AND  a.amount < @total_value_to_deduct 
    UNION ALL 
    SELECT crt.idx, 
      crt.amount, 
      crt.amount + prev.running_total AS running_total, 
      CASE 
       WHEN crt.amount + prev.running_total <= @total_value_to_deduct THEN 0 
       WHEN prev.running_total < @total_value_to_deduct AND crt.amount + prev.running_total > @total_value_to_deduct THEN crt.amount + prev.running_total - @total_value_to_deduct 
       ELSE crt.amount 
      END AS new_balance, 
      crt.RowNum 
    FROM CteRowNumber crt 
    INNER JOIN CteRecursive prev ON crt.RowNum = prev.RowNum + 1 
    --WHERE prev.running_total < @total_value_to_deduct 
) 
UPDATE @tbl1 
SET  balance = b.new_balance 
FROM @tbl1 a 

2)第2の溶液(SQL2012)

UPDATE @tbl1 
SET  balance = b.new_balance 
FROM @tbl1 a 
INNER JOIN 
(
    SELECT x.idx, 
      SUM(x.amount) OVER(ORDER BY x.idx) AS running_total, 
      CASE 
       WHEN SUM(x.amount) OVER(ORDER BY x.idx) <= @total_value_to_deduct THEN 0 
       WHEN SUM(x.amount) OVER(ORDER BY x.idx) - x.amount < @total_value_to_deduct --prev_running_total < @total_value_to_deduct 
       AND SUM(x.amount) OVER(ORDER BY x.idx) > @total_value_to_deduct THEN SUM(x.amount) OVER(ORDER BY x.idx) - @total_value_to_deduct 
       ELSE x.amount 
      END AS new_balance 
    FROM @tbl1 x 
) b ON a.idx = b.idx; 

3 )第3の解決策(SQ2000 +)はtriangular joinを使用します:

UPDATE @tbl1 
SET  balance = d.new_balance 
FROM @tbl1 e 
INNER JOIN 
(
    SELECT c.idx, 
      CASE 
       WHEN c.running_total <= @total_value_to_deduct THEN 0 
       WHEN c.running_total - c.amount < @total_value_to_deduct --prev_running_total < @total_value_to_deduct 
       AND c.running_total > @total_value_to_deduct THEN c.running_total - @total_value_to_deduct 
       ELSE c.amount 
      END AS new_balance 
    FROM 
    (
     SELECT a.idx, 
       a.amount, 
       (SELECT SUM(b.amount) FROM @tbl1 b WHERE b.idx <= a.idx) AS running_total 
     FROM @tbl1 a 
    ) c 
)d ON d.idx = e.idx; 
+0

+1 - 私のソリューションよりはるかに優れ、再帰的なCTEを実行しようとしましたが、動作させることができませんでした。 – Bridge

+0

連続した列にギャップがないことは確かですか? –

+0

いいえ、私は推測しません - 私はサンプルデータだけを見ていました。私は彼がそれの名前を変更したのを見るので、私はスキーマが修正されていないと思います!私は以前のコメントを編集しました。 – Bridge

0

各行の前の残高を使用してテーブルに新しい列を作成すると、INSERT/UPDATEでトリガーを使用して、新しく挿入された行の残高を作成できます。

1

"index"はキーワードであるため、そうでない場合は角カッコで囲んでください。

一般に、パフォーマンスのために行ごとに何かを行うのは良い考えではありません。

私が正しく読んでいる場合は、各残高列を金額列から@total_value_to_deduct変数を引いた値に設定するか、差額がマイナスになる場合は0に設定します。それが真実ならば、それを直接計算するだけではどうですか?あなたが期待した結果を投稿しなければ、私は自分の論理を二重にチェックすることはできませんが、私が間違っていればそれを修正してください。

UPDATE tbl1 
SET balance = CASE 
        WHEN amount < @total_value_to_deduct THEN 0 
        ELSE amount - @total_value_to_deduct 
       END 

編集:それは今より明らかだ質問への編集のための OK感謝。あなたは、すべての口座を順番に合計金額を取ろうとしています。私はこれを行うためのスクリプトを思いつくことができ、私の答えをさらに編集することができるかどうかがわかります。

編集#2: OK、私はすべての行を通じてinteratingずにそれを行うための方法を見つけることができませんでした(私は再帰CTEを試してみましたが、それは仕事を得ることができなかった)ので、私はやりましたもともとはあなたのようにしています。効果的に行ごとに3つのデータアクセスを行っている - 私は2にノックダウンしようとしたが、再び運がない。とにかくあなたが今持っているよりも速い場合に備えて私はそれを投稿しています。これは必要なすべてのコードでなければなりません(テーブルの作成/作成から離れて)。

DECLARE @id INT 
SELECT @id = Min([index]) 
FROM tbl1 

WHILE @id IS NOT NULL 
    BEGIN 
     UPDATE tbl1 
     SET balance = CASE 
         WHEN amount < @total_value_to_deduct THEN 0 
         ELSE amount - @total_value_to_deduct 
         END 
     FROM tbl1 
     WHERE [index] = @id 

     SELECT @total_value_to_deduct = CASE 
             WHEN @total_value_to_deduct < amount THEN 0 
             ELSE @total_value_to_deduct - amount 
             END 
     FROM tbl1 
     WHERE [index] = @id 

     SELECT @id = Min([index]) 
     FROM tbl1 
     WHERE [index] > @id 
END 
+0

申し訳ありません。私はより完全で合理的​​になるようにコードを書き直しました。 – skywills

+0

ありがとう - 私は今理解していると思う。もう一度試してみます。 – Bridge

+0

はい..すべての口座の合計金額を順次払おうとしています。お試しいただきありがとうございます! – skywills

1

ここでは、その方法の1つです。要求された金額以上の最初の実行合計を見つけ、この合計に関与するすべてのレコードを更新します。これはおそらく、 "toDeduct"列を導入する必要があり、当初はamountの値を持つという意味で、異なる書き方が必要です。 toDeduct = 0は、この行から何も差し引かれないことを意味するため、この更新で以前に使用されたデータセットを処理することができます。さらに、toDeduct、idxのインデックスは、無意味な検索/更新の数を減らすために使用するquick toDeduct <> 0フィルタを許可します。

declare @total_value_to_deduct int 
set @total_value_to_deduct = 130 

update tbl1 
set balance = case when balance.idx = tbl1.idx 
      then balance.sumamount - @total_value_to_deduct 
       else 0 
     end 
from tbl1 inner join 
(
    select top 1 * 
    from 
    (
      select idx, (select sum (a.amount) 
       from tbl1 a 
       where a.idx <= tbl1.idx) sumAmount 
      from tbl1 
    ) balance 
     where balance.sumamount >= @total_value_to_deduct 
     order by sumamount 
) balance 
    on tbl1.idx <= balance.idx 

これでカーソルが表示されます。一つは、単にカーソルFAST_FORWARDを宣言することで、パフォーマンスを得るでしょう:

declare csDeduct Cursor local fast_forward 
    for select idx, balance 
      from tbl1 
     where balance > 0 
     order by idx 

そして、あなたはfetch文を繰り返さないためにループをフェッチ書き直すかもしれません:

open csDeduct 
while 1 = 1 
begin 
    fetch next from csDeduct into @cs_index, @cs_balance 
    if @@fetch_status <> 0 
     break 

    if @cs_balance >= @total_value_to_deduct 
     set @deduct_amount = @total_value_to_deduct 
    else 
     set @deduct_amount = @cs_balance 

    -- contine deduct row by row if the total_value_to_deduct is not 0 
    set @total_value_to_deduct = @total_value_to_deduct - @deduct_amount 

    update tbl1 set balance = balance - @deduct_amount where idx = @cs_index 

end 
close csDeduct 
deallocate csDeduct 

は、選択カーソルの一部少し簡単に変更します。

+0

私の理解の乏しさのため申し訳ありませんが、私の理解のために "..."とカーソル部分の完全なコードを投稿することを心配します – skywills

+0

私はフェッチループを拡張しました。しかし、私が投稿した更新はカーソルと同じ仕事をします。パフォーマンスが向上しているかどうかを少し調べてみてください。 –

1

あなたのインデックスがギャップを持っていない場合は、最も簡単な解決策は

  • になりますが、再帰的な部分で、それを控除し、デクリメントした値で始まる、再帰CTEを作成します。
  • SQL文ROW_NUMBERを使用して

    ;WITH q AS (
        SELECT idx, amount, balance, 130 AS Deduct 
        FROM tbl1 
        WHERE idx = 1 
        UNION ALL 
        SELECT t.idx, t.amount, t.balance, q.Deduct - q.balance 
        FROM q 
          INNER JOIN @tbl1 t ON t.idx = q.idx + 1 
        WHERE q.Deduct - q.balance > 0 
    ) 
    UPDATE @tbl1 
    SET  Balance = CASE WHEN q.Balance - q.Deduct > 0 THEN q.Balance - q.Deduct ELSE 0 END 
    FROM q 
         INNER JOIN tbl1 t ON t.idx = q.idx 
    

    あなたはギャップの問題を軽減することができますが、それは、クエリを少し複雑に

あなたの実際のテーブルを更新するCTEの結果を使用してください。完全なサンプルコードを供給するための

;WITH r AS (
    SELECT idx, amount, balance, rn = ROW_NUMBER() OVER (ORDER BY idx) 
    FROM tbl1 
), q AS (
    SELECT rn, amount, balance, 130 AS Deduct, idx 
    FROM r 
    WHERE rn = 1 
    UNION ALL 
    SELECT r.rn, r.amount, r.balance, q.Deduct - q.balance, r.idx 
    FROM q 
      INNER JOIN r ON r.rn = q.rn + 1 
    WHERE q.Deduct - q.balance > 0 
) 
UPDATE tbl1 
SET  Balance = CASE WHEN q.Balance - q.Deduct > 0 THEN q.Balance - q.Deduct ELSE 0 END 
FROM q 
     INNER JOIN @tbl1 t ON t.idx = q.idx 

テストスクリプト

DECLARE @tbl1 TABLE (idx INTEGER, Amount INTEGER, Balance INTEGER) 
INSERT INTO @tbl1 (idx,amount,balance) VALUES (1, 50, 50) 
INSERT INTO @tbl1 (idx,amount,balance) VALUES (2, 30, 30) 
INSERT INTO @tbl1 (idx,amount,balance) VALUES (3, 20, 20) 
INSERT INTO @tbl1 (idx,amount,balance) VALUES (4, 50, 50) 
INSERT INTO @tbl1 (idx,amount,balance) VALUES (5, 60, 60) 

;WITH q AS (
    SELECT idx, amount, balance, 130 AS Deduct 
    FROM @tbl1 
    WHERE idx = 1 
    UNION ALL 
    SELECT t.idx, t.amount, t.balance, q.Deduct - q.balance 
    FROM q 
      INNER JOIN @tbl1 t ON t.idx = q.idx + 1 
    WHERE q.Deduct - q.balance > 0 
) 
UPDATE @tbl1 
SET  Balance = CASE WHEN q.Balance - q.Deduct > 0 THEN q.Balance - q.Deduct ELSE 0 END 
FROM q 
     INNER JOIN @tbl1 t ON t.idx = q.idx 

SELECT * 
FROM @tbl1 

出力

idx Amount Balance 
1 50  0 
2 30  0 
3 20  0 
4 50  20 
5 60  60 
関連する問題