SQL Server CTE运行余额

时间:2017-10-26 09:11:34

标签: sql-server

#TABLE1

idno | amount
-------------
1    | 700
2    | 500

#table2中

idno | amount1 | amount2 | amount3 | acctno
------------------------------------------
1    | 100     | 200     | 300     | 001
1    | 100     | 200     | 300     | 002
2    | 100     | 200     | 300     | 001

我想要发生的是将表2中的金额分别分配到表1的金额1,金额2,金额3中,然后获得余额并应用于下一行。我已经尝试过使用CTE,但仍然坚持将运行余额传递给下一行。

查询:

Declare @table2 TABLE (idno varchar(max), amount1 decimal,amount2 
decimal,amount3 decimal,acctno varchar(max))
INSERT INTO @table2 VALUES
('1',100,200,300,'001'),
('1',100,200,300,'002'),
('2',100,200,300,'001')
Declare @table1 TABLE (idno varchar(max), amount decimal)
INSERT INTO @table1 VALUES
('1',700),
('2',500);
WITH due AS (SELECT a.idno,a.amount,b.acctno,b.amount1,b.amount2,b.amount3
              from @table1 a left join @table2 b on a.idno = b.idno),
      payment AS (SELECT *,case when amount-amount1<0 then amount 
              else amount1 end as amount1pay
              ,case when amount-amount1<=0 then 0 
              when amount-amount1-amount2 <0 then amount-amount1
              else amount2 end as amount2pay ,
              case when amount-amount1-amount2<=0 then 0 
              when amount-amount1-amount2-amount3<0 
              then amount-amount1-amount2 else amount3 end as amount3pay
              FROM due),
      payment2 AS (SELECT SUM(amount-amount1pay-amount2pay-amount3pay) 
                   OVER ( PARTITION BY idno ORDER BY acctno
             ROWS  UNBOUNDED PRECEDING  ) as balance,* FROM payment)

select * from payment2

当前结果

balance | idno | amount | acctno | amount1 | amount2 | amount3 | amount1pay | amount2pay | amount3pay
---------------------------------------------------------------------------------------------------------
  100   |    1 |    200 |   001  |     100 |     200 |    300  |    100     |    200     |    300
  200   |    1 |    200 |   002  |     100 |     200 |    300  |    100     |    200     |    300
    0   |    2 |    500 |   001  |     100 |     200 |    300  |    100     |    200     |    200

预期结果

balance | idno | amount | acctno | amount1 | amount2 | amount3 | amount1pay | amount2pay | amount3pay
---------------------------------------------------------------------------------------------------------
  100   |    1 |    200 |   001  |     100 |     200 |    300  |    100     |    200     |    300
  100   |    1 |    200 |   002  |     100 |     200 |    300  |    100     |      0     |      0
    0   |    2 |    500 |   001  |     100 |     200 |    300  |    100     |    200     |    200

1 个答案:

答案 0 :(得分:1)

这看起来非常混乱,因为这里有很多不同的事情。我不认为我完全理解你如何分配这笔钱的所有“规则”,但这个查询确实产生了预期的结果(实际上它略有不同,但我认为你的表中有一个错误,你显示“200 “对于前两行的金额应该是”700“)?

WITH Base AS (
    SELECT
        t1.idno,
        t2.acctno,
        t1.amount,
        t2.amount1,
        t2.amount2,
        t2.amount3,
        ROW_NUMBER() OVER (ORDER BY t1.idno, t2.acctno) AS row_id
    FROM
        @table1 t1
        INNER JOIN @table2 t2 ON t1.idno = t2.idno),
RunningBalance AS (
    SELECT
        *,
        CASE WHEN amount > amount1 + amount2 + amount3 THEN amount - amount1 - amount2 - amount3 ELSE 0 END AS new_balance
    FROM
        Base),
NewIdno AS (
    SELECT
        idno,
        MIN(row_id) AS first_row_id
    FROM
        Base
    GROUP BY
        idno),
NewBalance AS (
    SELECT
        n.first_row_id AS row_id,
        b.amount
    FROM
        NewIdno n
        INNER JOIN Base b ON b.row_id = n.first_row_id),
Amount1 AS (
    SELECT
        b.row_id,
        rb1.new_balance AS balance,
        b.idno,
        b.amount,
        b.acctno,
        b.amount1,
        b.amount2,
        b.amount3,
        CASE WHEN ISNULL(n.amount, rb2.new_balance) >= b.amount1 THEN b.amount1 ELSE b.amount1 - ISNULL(n.amount, rb2.new_balance) END AS pay_amount1,
        ISNULL(n.amount, rb2.new_balance) - b.amount1 AS carried_forward_1
    FROM
        Base b
        INNER JOIN RunningBalance rb1 ON rb1.row_id = b.row_id
        LEFT JOIN RunningBalance rb2 ON rb2.row_id = b.row_id - 1
        LEFT JOIN NewBalance n ON n.row_id = b.row_id),
Amount2 AS (
    SELECT
        *,
        CASE WHEN carried_forward_1 >= amount2 THEN amount2 ELSE carried_forward_1 END AS pay_amount2,
        carried_forward_1 - CASE WHEN carried_forward_1 >= amount2 THEN amount2 ELSE carried_forward_1 END AS carried_forward_2
    FROM
        Amount1),
Amount3 AS (
    SELECT
        *,
        CASE WHEN carried_forward_2 >= amount3 THEN amount3 ELSE carried_forward_2 END AS pay_amount3
FROM
        Amount2)
SELECT
    balance,
    idno,
    amount,
    acctno,
    amount1,
    amount2,
    amount3,
    pay_amount1,
    pay_amount2,
    pay_amount3
FROM
    Amount3;

我的结果是:

balance idno    amount  acctno  amount1 amount2 amount3 pay_amount1 pay_amount2 pay_amount3
100     1       700     001     100     200     300     100         200         300
100     1       700     002     100     200     300     100         0           0
0       2       500     001     100     200     300     100         200         200

我正在使用的一些规则:

  • 你从表1的金额开始分配,即idno#1为700英镑,idno#2为500英镑;
  • 这是由数字顺序的acctnos分配给表2;
  • 如果还有什么东西需要支付,那么你需要从上一个算盘中结转多个acctnos;
  • 一旦你开始一个新的idno,你就不能继续前一个剩余的钱。

那它是如何运作的?

步骤1 - 订购数据并添加索引(row_id)

这只是两个表中的基础数据,显示了我们需要分配的数量以及我们分配的行数:

idno    acctno  amount  amount1 amount2 amount3 row_id
1   001 700 100 200 300 1
1   002 700 100 200 300 2
2   001 500 100 200 300 3

第2步 - 计算出运行余额

如果我们分配每行可用的全部金额,这将计算剩余多少钱:

idno    acctno  amount  amount1 amount2 amount3 row_id  new_balance
1   001 700 100 200 300 1   100
1   002 700 100 200 300 2   100
2   001 500 100 200 300 3   0

第3步 - (插曲)我们需要知道我们将在第一个

分配数据的行

这只是每个idno的第一个row_id:

idno    first_row_id
1   1
2   3

第4步 - 稍微浪费,因为我们可以在最后一步中完成这项工作

我们只需要将总数分配到每个“第一”行:

row_id  amount
1   700
3   500

第5步 - 这是我们正确处理运行平衡的地方

对于每一行,规则是我们从“新余额”开始,该新余额仅存在于每个要分配的金额的第一行。如果这不是第一行,那么我们使用运行余额,但我们从前一行(rb2.row_id = b.row_id - 1)获取此值。我们将始终拥有其中一个:

row_id  balance idno    amount  acctno  amount1 amount2 amount3 pay_amount1 carried_forward_1
1   100 1   700 001 100 200 300 100 600
2   100 1   700 002 100 200 300 100 0
3   0   2   500 001 100 200 300 100 400

因此,结转不是下一行的内容,而是要分配给下一个数量(在这种情况下为2)。

请注意,这适用于您的数据集,但如果每个idno有两行以上,则无效。如果每个idno有两行以上,那么你只需要添加另一个阶段来处理这个场景。

第6步 - 继续

对于每个金额,我们需要将其从先前计算结转的金额中扣除,给出我们可以分配给该金额的金额,以及结转到下一次计算的金额(对于金额3来说是多余的)因为没有金额4)。