SQL Server - 银行转帐表中的更新余额字段

时间:2018-03-09 04:29:28

标签: sql-server sql-update

我有一个存储类似银行对帐单的交易的表。例如:

ID |Date        |Amount    |Balance  |
--------------------------------------
1  |01/01/2018  |    100.00|   100.00|
2  |01/02/2018  |     50.00|   150.00|
3  |01/04/2018  |     -5.00|   145.00|
4  |01/05/2018  |     10.00|   155.00|

在理想的世界中,交易将按时间顺序插入,因此最后一笔交易之前的余额不应该更新,但事实并非如此。我们假设我们添加了一个日期为01/03/2018的交易,后续余额应该更新,结果表应该是:

ID |Date        |Amount    |Balance  |
--------------------------------------
1  |01/01/2018  |    100.00|   100.00|
2  |01/02/2018  |     50.00|   150.00|
3  |01/04/2018  |     -5.00|   165.00|
4  |01/05/2018  |     10.00|   175.00|
5  |01/03/2018  |     20.00|   170.00|    

但按日期排序应该如下:

ID |Date        |Amount    |Balance  |
--------------------------------------
1  |01/01/2018  |    100.00|   100.00|
2  |01/02/2018  |     50.00|   150.00|
5  |01/03/2018  |     20.00|   170.00|
3  |01/04/2018  |     -5.00|   165.00|
4  |01/05/2018  |     10.00|   175.00| 

事务可以随任何日期进入,甚至在表中最旧的事务之前,代码应该能够在有序或无序(按日期字段)表中插入行。

我该怎么做?提前谢谢!

2 个答案:

答案 0 :(得分:2)

假设表中的现有余额已处于正确状态。

我尝试在SQL更新语句中使用变量解决此问题。 假设我们有初始数据 -

declare @xyz table (ID int,[Date] date,Amount decimal(10,2),Balance decimal(10,2))

insert into @xyz (ID, [Date], Amount, Balance)
select 1  ,'01/01/2018',    100.00,   100.00 union all
select 2  ,'01/02/2018',     50.00,   150.00 union all
select 3  ,'01/04/2018',     -5.00,   145.00 union all
select 4  ,'01/05/2018',     10.00,   155.00 union all
select 5  ,'01/05/2018',     30.00,   185.00 union all
select 6  ,'01/07/2018',     25.00,   250.00 union all
select 7  ,'01/06/2018',     40.00,   225.00

现在,应用程序向我们发送带有这些值的新插入 -

declare
    @date date = '12/08/2017',
    @amount decimal(10,2) = 20.0

以上新事务值发送到过程和程序内部的代码如下所示 -

declare @maxDate date = (select max([date]) from @xyz)
declare @balance decimal(10,2) = 
(select top 1 Balance + @amount from @xyz where [Date] < @date order by [date] desc,ID desc)

insert into @xyz (ID, Date, Amount, Balance)
select 8, @date, @amount, isnull(@balance,@amount)

if @maxDate > @date
    update x
        set Balance = Balance + @amount
        from @xyz as x
        where Date > @date

这对我有用,但如果不正确,请告诉我。

答案 1 :(得分:1)

Bhatia Ashish的答案确实是一个非常巧妙的答案,但它错过了一个关键点。也就是说,如果一次添加多个新行,它会中断。我重新使用了这种方法并使其完全具有设置意识。享受:

-- Synched data
declare @t table (
    Id int identity(1,1) primary key,
    CreateDate date not null,
    Amount money not null,
    Balance money not null
);

-- New rows
declare @newdata table (
    CreateDate date not null,
    Amount money not null,
    -- Running total of balance changes
    RT money null,
    -- Previous balance value from original data
    PrevBalance money null
);

-- Original data, correct balances
insert into @t (CreateDate, Amount, Balance)
values
    ('20180101', $100, $100),
    ('20180102', $50, $150),
    ('20180104', $-5, $145),
    ('20180105', $10, $155),
    ('20180108', $24, $179),
    ('20180110', $-17, $162),
    ('20180111', $-11, $151);

-- DEBUG: Before sync
select * from @t t order by t.CreateDate;

-- New rows
insert into @newdata (CreateDate, Amount)
values
    ('20171227', $41),
    ('20180103', $20),
    ('20180106', $-100),
    ('20180107', $36),
    ('20180109', $29);


-- Materialise window functions for the update source
with cte as (
    select n.CreateDate,
        sum(n.Amount) over(order by n.CreateDate) as [RT]
    from @newdata n
)
update n set RT = c.RT, PrevBalance = isnull(ca.Balance, $0)
from @newdata n
    inner join cte c on c.CreateDate = n.CreateDate
    outer apply (
        select top (1) t.Balance from @t t
        where t.CreateDate < n.CreateDate
        order by t.CreateDate desc
    ) ca;

-- DEBUG: New transactions with supplementary data
select * from @newdata;

-- Put new rows into main table
insert into @t (CreateDate, Amount, Balance)
select n.CreateDate, n.Amount, $0
from @newdata n;

-- Correct balances for all transactions affected
update t set Balance = ca.RT
    + case
        when t.CreateDate = ca.CreateDate then ca.PrevBalance
        else t.Balance
    end
from @t t
    cross apply (
        select top (1) n.CreateDate, n.RT, n.PrevBalance
        from @newdata n where n.CreateDate <= t.CreateDate
        order by n.CreateDate desc
    ) ca;

-- DEBUG: After sync
select * from @t t order by t.CreateDate;

编辑:更正了其中一个插入的交易发生的时间早于任何现有交易。