在Oracle表中生成银行帐户余额

在Oracle表中生成银行帐户余额,oracle,plsql,oracle11g,Oracle,Plsql,Oracle11g,我在Oracle 11g数据库中有一个accounts表和一个movements表。它们的工作方式与您希望银行账户的工作方式相同。它们的简化版本是 CREATE TABLE accounts ( id NUMERIC(20) NOT NULL -- PK ); CREATE TABLE movements ( id NUMERIC(20) NOT NULL, -- PK account_id NUMERIC(20) NOT NULL, -- FK to accounts

我在Oracle 11g数据库中有一个accounts表和一个movements表。它们的工作方式与您希望银行账户的工作方式相同。它们的简化版本是

CREATE TABLE accounts (
    id NUMERIC(20) NOT NULL -- PK
);

CREATE TABLE movements (
    id NUMERIC(20) NOT NULL, -- PK
    account_id NUMERIC(20) NOT NULL, -- FK to accounts table
    stamp TIMESTAMP NOT NULL, -- Movement creation timestamp
    amount NUMERIC(20) NOT NULL,
    balance NUMERIC(20) NOT NULL
);
你有一个账户,一些移动是间接创建的,每个移动都有一个给定的金额。例如,我希望
移动
表中包含以下数据:

| id | account_id |       stamp         |  amount | balance |
-------------------------------------------------------------
|  1 |     1      | 2016-12-29 00:00:01 |   50.00 |   50.00 |
|  2 |     1      | 2016-12-29 00:00:02 |   80.00 |  130.00 |
|  3 |     1      | 2016-12-29 00:00:03 |  -15.00 |  115.00 |
-------------------------------------------------------------
我的问题是,如何更新
余额

我在存储过程中进行插入(
INSERT-INTO-movements…SELECT-FROM…
),因此可以在同一查询中、在以后的
更新中或使用纯PLSQL进行插入

我可以想出两种方法:

  • 插入后的
    更新
    ,类似(想法,未经测试):

    我的问题是,它是否按顺序执行?从第一乐章到最后一乐章?或者oracle可能在没有特定顺序的情况下运行此操作,生成这样的场景:例如,第三个移动在第二个移动之前更新,因此第二个移动的余额仍然过时

  • 游标:我可以定义一个游标并在有序的移动列表上运行循环,在每次迭代中读取帐户的上一个余额,并计算当前余额,使用
    更新设置它

    通过这种方式,我可以确定余额是按顺序更新的,但由于性能问题,我总是避免使用游标。此存储过程每次将处理数百条记录,而移动表将存储数百万条记录。这样,性能会成为一个问题吗

  • 我的最后一个问题是,考虑到性能,生成
    余额
    列数据的最佳方法是什么?

    编辑-动作创建说明 我想我对这部分不太清楚。在执行SP时,我正在创建多个不同帐户的多个移动,这就是为什么我提到移动创建是通过以下方式完成的

    -- Some actions
    
    INSERT INTO movements (account_id, stamp, amount, balance)
    SELECT ... FROM several_tables_with_joins;
    
    -- More actions
    

    这就是为什么我提到余额可以在同一个查询中生成,也可以在以后的
    更新中生成,或者在某个注释中提到的触发器之类的其他方法中生成。

    我想我会将余额保留在ACCOUNTS表中。然后,在插入移动记录时,更新相应的帐户记录

    “考虑到性能,生成余额列数据的最佳方法是什么?”

    通常,每笔交易后对汇总列的持续维护比按需计算要花费更大的成本。然而,账户余额是一种特殊情况,因为我们确实需要在每次交易后了解它,以检查(比如)账户是否出现赤字或超过透支限额

    关键的洞察是:在我们处理一个新的运动之前,我们已经知道当前的平衡。这是最新运动记录的平衡值

    啊,但是我们怎么知道哪个动作记录是最新的呢?有各种不同的解决方案,但最简单的是一个丑陋的
    is\u latest
    标志。这不仅提供了获取最新移动记录的简单方法,还提供了可锁定的目标,这在多用户环境中非常重要。我们需要确保在任何给定时间只有一个事务在操纵余额

    因此,您的存储过程将类似于:

    create or replace procedure new_movement 
       ( p_account_id in movements.account_id%type
         , p_amount in movements.amount%type )
    is
        cursor c_curr_bal (p_acct_id movements.account_id%type) is
            select balance 
            from movements
            where account_id = p_acct_id
            and is_latest = 'Y'
            for update of is_latest; 
        l_balance movements.balance%type;
        new_rec movements%rowtype;
    begin
        open c_curr_bal(p_account_id);
        fetch c_curr_bal into l_balance;
    
        new_rec.id := movements_seq.nextval;
        new_rec.account_id := p_account_id;
        new_rec.stamp := systimestamp;
        new_rec.amount := p_amount;
        new_rec.balance := l_balance + p_amount;
        new_rec.is_latest := 'Y';
    
        update movements
        set is_latest = null
        where current of c_curr_bal;
    
        insert into movements
        values new_rec;    
    
        close c_curr_bal;
    
        commit; -- need to free the lock
    end new_movement;
    /    
    


    is_latest
    标志的另一种选择是将当前余额作为ACCOUNTS表中的一列进行维护。逻辑是一样的,只需选择ACCOUNTS表来更新当前余额即可

    插入或更新后的
    Trigger
    是解决方案,并且在触发器查询中是帐户的最后一个
    余额
    ?它不会太重吗?这在多用户环境中不起作用。如果两个用户几乎同时为同一个帐户输入不同的事务怎么办?两人在开始输入自己的交易时都不知道其他交易。您需要序列化事务,而您的用户会因此讨厌您。最好创建一个动态计算余额的视图(然后可能使其成为提交时快速刷新的MV)。锁定
    is\u最新
    记录的句子是什么?
    更新
    ?那么它不应该在开始时,在读取最后一个
    余额
    值之前吗?另外,请查看问题中我的编辑。选择更新会在
    余额
    记录上发出锁定。因此,当光标打开时,它在事务开始时被锁定。
    create or replace procedure new_movement 
       ( p_account_id in movements.account_id%type
         , p_amount in movements.amount%type )
    is
        cursor c_curr_bal (p_acct_id movements.account_id%type) is
            select balance 
            from movements
            where account_id = p_acct_id
            and is_latest = 'Y'
            for update of is_latest; 
        l_balance movements.balance%type;
        new_rec movements%rowtype;
    begin
        open c_curr_bal(p_account_id);
        fetch c_curr_bal into l_balance;
    
        new_rec.id := movements_seq.nextval;
        new_rec.account_id := p_account_id;
        new_rec.stamp := systimestamp;
        new_rec.amount := p_amount;
        new_rec.balance := l_balance + p_amount;
        new_rec.is_latest := 'Y';
    
        update movements
        set is_latest = null
        where current of c_curr_bal;
    
        insert into movements
        values new_rec;    
    
        close c_curr_bal;
    
        commit; -- need to free the lock
    end new_movement;
    /