我在Oracle 11g数据库中有一个帐户表和一个移动表。它们的工作方式与您希望银行帐户的工作方式相同。它们的简化版本是
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
);
您有一个帐户,并且会创建一些具有给定金额的动作。例如,我希望以下数据位于movements
表中:
| 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 |
-------------------------------------------------------------
我的问题是,如何更新balance
列?
我在存储过程(INSERT INTO movements ... SELECT FROM ...
)中进行插入,因此可以在同一查询内,稍后UPDATE
或纯PLSQL中完成。
我可以想到两种方法:
插入后的UPDATE
,类似(一个想法,未经测试):
UPDATE movements um
SET balance = (um.amount + (SELECT m.balance
FROM movements m
WHERE m.account_id = um.account_id
AND rownum = 1
ORDER BY stamp DESC)) -- last balance from same account?
WHERE stamp > :someDate; -- To limit the updated records
我的问题是,它是按顺序执行的吗?从第一乐章到最后一乐章?或者oracle可能在没有特定顺序的情况下运行它,例如,生成第三个移动在第二个移动之前更新的场景,所以第二个移动的余额仍然过时了?
游标:我可以定义游标并在有序的移动列表上运行循环,在每次迭代中读取帐户的先前余额,并计算当前余额,并使用UPDATE
进行设置
这样我就可以确定天平是按顺序更新的,但由于性能问题,我总是避免使用游标。此存储过程每次将处理数百条记录,并且移动表将存储数百万条记录。这种表现会成为一个问题吗?
我的最后一个问题是,考虑到效果,生成balance
列数据的最佳方式是什么?
我想我对这一部分并不太清楚。在我的SP执行的那一刻,我正在创建几个不同帐户的几个动作,这就是为什么我提到动作创建是用
之类的东西完成的。-- Some actions
INSERT INTO movements (account_id, stamp, amount, balance)
SELECT ... FROM several_tables_with_joins;
-- More actions
这就是为什么我提到可以在同一个查询中,在稍后的UPDATE
或其他一些方法中生成余额,例如其中一个评论中提到的触发器。
答案 0 :(得分:1)
“考虑性能,生成平衡列数据的最佳方法是什么”
通常,每次交易后对总计列的持续维护会产生比仅根据需要计算它们更重的成本。但是,帐户余额是一种特殊情况,因为我们确实需要在每次交易后知道它,以检查帐户是否已经变为红色或超出透支限额。
关键见解是:在我们处理新动作之前,我们已经知道了当前的余额。对于最新的MOVEMENT记录,这是BALANCE的价值。
啊,但我们怎么知道最新的MOVEMENT记录?对此有各种不同的解决方案,但最简单的是一个丑陋的is_latest
标志。这不仅提供了一种简单的方法来获取最新的MOVEMENT记录,它提供了一个可锁定的目标,这在多用户环境中很重要。我们需要确保在任何给定时间只有一个交易操纵余额。
因此,您的存储过程将类似于:
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表FOR FOR UPDATE OF CURRENT_BALANCE。
答案 1 :(得分:0)
我想我会把BALANCE保留在ACCOUNTS表中。然后,当您插入MOVEMENTS记录时,您将更新相应的ACCOUNT记录。