3

支払いのテーブルを取得し、関連するテーブルに記載されている一連のルール (基本的には一連のバケット) に従ってそれらの支払いを分配しようとする SQLServer 2008 ストアド プロシージャを作成しています。ただし、分配 (支払い値をバケツに入れる) が、現在頭を悩ませている原因です。

テーブル Payments には支払う値が含まれており、テーブル Buckets は、支払われる初期値が使い果たされる (0 に達する) まで、各バケットにどれだけ入れるべきかがすべてであるとします。

次の表を例として使用します (各支払いに適したバケットを選択するための複雑な基準があるため、実際の使用例はもう少し不自然です)。

PaymentId     Value                 BucketId       MaxAmount
--------------------------          --------------------------------
1             16.5                  1              5.5
2             7.0                   2              10
                                    3              8.3

支払い 1 の場合: 5.5 単位 (そのバケットの最大) がバケット 1 に、10 単位がバケット 2 に (11.5 はバケット 1 の残りですが、バケット 2 の最大値を超えています)、1 単位 (16.5 - 5.5 - 10) になります。 ) をバケット 3 に入れる必要があります。すべての支払いについて繰り返します。

これは、任意の命令型言語で簡単に実装でき、おそらく for/while ループを使用する SQL でも実装できますが、より良い方法があるかどうかを認識しようとしていました (移植性がなく、SQLServer 2005 以降に固有のものであっても)。

私は(主に再帰的なCTEについて)いくつかの調査を行いましたが、本当に素晴らしいものは何も思い浮かびません。SQL-fuを備えたStackOverflowersが頭から答えていると確信しているので、そこに出して見てみようと思いました...

どうもありがとうございました。

4

3 に答える 3

6

バケット テーブルを一時テーブルに配置し、現在の合計と呼ばれる追加の列を作成します。これにより、この結合までの現在の合計が得られ、次に支払いと tempbucket テーブルをクロス結合し、支払い <= tempbucket テーブルの現在の合計の条件を指定します。これで問題が解決するはずです。Mafu Josh の DDL を使用して以下のクエリを作成したので、彼に感謝します。OPは常にこれらのものを投稿して、他の人の生活を楽にするべきだと思います。

buckte テーブルは非常に小さいように見えますが、テーブルが非常に大きい場合は、現在の合計を生成するには、変数を使用して更新します。これは、以下の方法よりも効率的です。私には、このテーブルは多かれ少なかれ静的であると思われます。したがって、テーブル自体の一部として累計を作成できます。

   DECLARE @Buckets TABLE ( 
    BucketId INT, 
    MaxAmount DECIMAL(18,6) 
) 

INSERT INTO @Buckets VALUES (1, 5.5) 
INSERT INTO @Buckets VALUES (2, 10) 
INSERT INTO @Buckets VALUES (3, 8.3) 

DECLARE @Payments TABLE ( 
    PaymentId INT, 
    Value DECIMAL(18,6) 
) 

INSERT INTO @Payments VALUES (1,16.5) 
INSERT INTO @Payments VALUES (2,7.0) 
INSERT INTO @Payments VALUES (3,23.8) 

DECLARE @tempBuckets TABLE ( 
    BucketId INT, 
    MaxAmount DECIMAL(18,6) ,
    currentruntotal decimal(18,6)
) 
insert into @tempBuckets select bucketid,maxamount ,(select SUM(maxamount) from @Buckets bin where b.bucketid >=bin.bucketid)
--,isnull((select SUM(maxamount) from @Buckets bin where b.bucketid > bin.bucketid),0)
from @Buckets b

select * from @tempBuckets
select PaymentId,Value,BucketId,
case when p.Value >= tb.currentruntotal then tb.MaxAmount else p.Value - tb.currentruntotal + tb.MaxAmount end as bucketamount
from @Payments p inner join @tempBuckets tb on  (p.Value >= tb.currentruntotal or p.Value between tb.currentruntotal - tb.MaxAmount and tb.currentruntotal )
order by PaymentId
go
于 2012-07-24T04:30:48.543 に答える
4

カーソルを使用しない私の試み:

DECLARE @Buckets TABLE (
    BucketId INT,
    MaxAmount DECIMAL(18,6)
)

INSERT INTO @Buckets VALUES (1, 5.5)
INSERT INTO @Buckets VALUES (2, 10)
INSERT INTO @Buckets VALUES (3, 8.3)

DECLARE @Payments TABLE (
    PaymentId INT,
    Value DECIMAL(18,6)
)

INSERT INTO @Payments VALUES (1,16.5)
INSERT INTO @Payments VALUES (2,7.0)

SELECT
  P1.PaymentId
, P1.Value as TotalPayment
, B4.BucketId
, B4.MaxAmount
, CASE WHEN B3.BucketId = B4.BucketId THEN P1.Value - MaxAmountRunningTotalOfPreviousBuckets ELSE B4.MaxAmount END AS BucketPaymentAmount
FROM @Payments P1
INNER JOIN (
    SELECT
      B2.BucketId
    , B2.MaxAmount as BucketMaxAmount
    , SUM(B1.MaxAmount) - B2.MaxAmount as MaxAmountRunningTotalOfPreviousBuckets
    FROM @Buckets B1
    INNER JOIN @Buckets B2
      ON B1.BucketId <= B2.BucketId
    GROUP BY B2.BucketId, B2.MaxAmount
  ) AS B3
  ON P1.Value > B3.MaxAmountRunningTotalOfPreviousBuckets AND P1.Value <= (B3.MaxAmountRunningTotalOfPreviousBuckets + BucketMaxAmount)
INNER JOIN @Buckets B4
  ON B4.BucketId <= B3.BucketId
ORDER BY P1.PaymentId, B3.BucketId
于 2012-07-23T23:53:24.010 に答える
2

再帰的な CTE アプローチを次に示します。

WITH BucketsRanked AS (
  SELECT *, rnk = ROW_NUMBER() OVER (ORDER BY BucketId) FROM Buckets
)
, PaymentsRanked AS (
  SELECT *, rnk = ROW_NUMBER() OVER (ORDER BY PaymentId) FROM Payments
)
, PaymentsDistributed AS (
  SELECT
    b.BucketId,
    p.PaymentId,
    Bucket        = b.MaxAmount,
    Payment       = p.Value,
    BucketRnk     = b.rnk,
    PaymentRnk    = p.rnk,
    BucketPayment = CASE
                      WHEN p.Value > b.MaxAmount
                      THEN b.MaxAmount
                      ELSE p.Value
                    END,
    CarryOver     = p.Value - b.MaxAmount
  FROM
    BucketsRanked b,
    PaymentsRanked p
  WHERE b.rnk = 1 AND p.rnk = 1
  UNION ALL
  SELECT
    b.BucketId,
    p.PaymentId,
    Bucket        = b.MaxAmount,
    Payment       = p.Value,
    BucketRnk     = b.rnk,
    PaymentRnk    = p.rnk,
    BucketPayment = CASE
                      WHEN x.PaymentValue > x.BucketValue
                      THEN x.BucketValue
                      ELSE x.PaymentValue
                    END,
    CarryOver     = x.PaymentValue - x.BucketValue
  FROM PaymentsDistributed d
    INNER JOIN BucketsRanked  b
      ON b.rnk = d.BucketRnk  + CASE SIGN(CarryOver) WHEN -1 THEN 0 ELSE 1 END
    INNER JOIN PaymentsRanked p
      ON p.rnk = d.PaymentRnk + CASE SIGN(CarryOver) WHEN +1 THEN 0 ELSE 1 END
    CROSS APPLY (
      SELECT
        CONVERT(
          decimal(18,6),
          CASE SIGN(CarryOver) WHEN -1 THEN -d.CarryOver ELSE b.MaxAmount END
        ),
        CONVERT(
          decimal(18,6),
          CASE SIGN(CarryOver) WHEN +1 THEN +d.CarryOver ELSE p.Value     END
        )
    ) x (BucketValue, PaymentValue)
)
SELECT
  BucketId,
  PaymentId,
  Bucket,
  Payment,
  BucketPayment
FROM PaymentsDistributed
;

基本的に、このクエリは最初の支払いと最初のバケットを取得し、どちらが少ないかを判断して最初のBucketPaymentアイテムを生成します。支払い値とバケット容量の差は、次の反復で使用するために記憶されます。

次の反復では、その符号に応じて、差額がバケット金額または支払いのいずれかとして使用されます。また、差の符号に応じて、クエリはPaymentsテーブルから次の支払いを取得するか、 から次のバケットを取得しBucketsます。(ただし、差が 0 の場合、クエリは実際には次の支払いと次のバケットの両方を取得します。) 最初の反復と同じロジックが、新しいバケットの金額と支払いの値に適用されます。

反復は、バケットまたは支払いがなくなるまで続きます。または、デフォルトの MAXRECURSION 値の 100 に達するまで。この場合は、追加する必要があります。

OPTION (MAXRECURSION n)

ここでn、反復 (再帰) の最大数を指定する 32767 までの負でない整数でなければなりません。(これ0は実際にはunlimitedを表すことに注意してください。)

このクエリは SQL Fiddle で試すことができます。

于 2012-07-24T07:20:02.373 に答える