当值是 inserted/updated/deleted to/from 同一 table 中的另一列时,如何让列自动反映值?

How to let a column have values reflected automatically, when value is inserted/updated/deleted to/from another column in the same table?

我创建了 2 个表:INFORMATIONFEED

INFORMATION has 2 attributes : ID(Primary Key), TOT_AMOUNT.

FEED has 4 attributes : ID(Foreign key refer INFORMATION(ID)), S_AMOUNT, S_DATE, TOT_REM.

现在,我必须 insert/update/delete 值 to/from TOT_REM,基于 S_AMOUNTTOT_AMOUNT 的 insertion/removal/update。

示例内容为:

INFORMATION Table
------------------
ID   |  TOT_AMOUNT
1    |    100
2    |    20
3    |    50
...

               FEED Table
----------------------------------------
ID   |   S_AMOUNT   |  S_DATE  | TOT_REM 
1    |     10       |10.10.2010|   90
1    |     10       |13.10.2010|   80
1    |     30       |17.10.2013|   50
1    |     10       |20.10.2016|   40
...

我们需要在 S_AMOUNT 上执行的 update/insert/delete 操作的基础上,借助 TOT_AMOUNT & [=22] 自动将值插入到 TOT_REM 属性中=].

在任何时候,TOT_REM 都不能小于 0。而且,TOT_REM 需要自动 inserted/removed/updated 这样

TOT_REM for i(at a specific date) = (TOT_AMOUNT for ID=i) - 
                                    SUM(S_AMOUNT of all instances of ID=i,
                                    which is later than the S_DATE for ID=i); 

因此,假设如果我们删除第二个元组(1,10,'13.10.2010',80),BR_FEED 的反射状态应该是:

               FEED Table
----------------------------------------
ID   |   S_AMOUNT   |  S_DATE  | TOT_REM 
1    |     10       |10.10.2010|   90
1    |     30       |17.10.2013|   60
1    |     10       |20.10.2016|   50
...

我写了一个触发器,显示失败

ORA-04091: table SSUMAN.FEED is mutating, trigger/function may not see it

触发器代码为:

CREATE OR REPLACE TRIGGER BR_INSERT_TRB
AFTER DELETE OR INSERT OR UPDATE OF S_AMOUNT ON FEED
FOR EACH ROW
BEGIN

IF DELETING THEN 
UPDATE  FEED bf
SET     bf.TOT_REM = bf.S_AMOUNT + :OLD.S_AMOUNT;
END IF;

IF INSERTING THEN 
INSERT INTO FEED (TOT_REM) VALUES(
((SELECT TOT_AMOUNT FROM INFORMATION bi WHERE bi.ID=:NEW.ID) - 
(SELECT SUM(S_AMOUNT) FROM FEED bf where bf.ID=:NEW.ID) - 
:NEW.S_AMOUNT);
END IF;

IF UPDATING THEN 
UPDATE  FEED bf
SET     bf.TOT_REM = (SELECT TOT_AMOUNT FROM BR_INFORMATION bi WHERE bi.ID=bf.ID) - 
(SELECT SUM(S_AMOUNT) FROM FEED bf where bf.ID=:NEW.ID) - 
 :NEW.S_AMOUNT
WHERE   :NEW.ID IS NOT NULL;
END IF;   

END;

问题:

  1. 这种方法有缺陷吗?我不能通过这种方式实现我想要的吗?[可选]
  2. 这里有没有引入视图的范围?我无法在那条线上思考!可能是缺乏经验...[可选]
  3. 有什么更好的方法,可以自动反映 TOT_REM 个值?[必须回答]

我觉得还是创建视图比较好。看看这个

测试数据

create table feed(ID,S_AMOUNT,S_DATE) as  (
  SELECT 1,10, TO_DATE('10.10.2010','dd.mm.yyyy') FROM dual UNION all
  SELECT 1,10,TO_DATE('13.10.2010','dd.mm.yyyy') FROM dual UNION all
  SELECT 1,30,TO_DATE('17.10.2013','dd.mm.yyyy') FROM dual UNION all
  SELECT 2,10,TO_DATE('20.10.2016','dd.mm.yyyy') FROM dual)

create table INFORMATION (id, TOT_AMOUNT) as (
  SELECT 1,100 FROM DUAL UNION ALL
  SELECT 2,20 FROM DUAL UNION ALL
  SELECT 3,50 FROM DUAL)

查询

create or replace view result_feed as
  SELECT f.*,i.TOT_AMOUNT - NVL(SUM(S_AMOUNT) OVER(PARTITION BY f.ID ORDER BY f.S_DATE),0) AS tot_rem FROM FEED f, INFORMATION i
    WHERE f.ID = i.id
  ORDER BY f.ID, f.S_DATE;
  -- used NVL to prevent side-effect of null values


  SELECT * from RESULT_FEED;

在这种情况下,您使用触发器的方法不合适table。我认为很少添加数据,只有在特殊情况下才需要查询。当然,有一些方法可以解决变异 table(包变量、复合触发器、自主事务),但我认为它们只会给你的数据库增加性能问题。

如果这是我的业务问题,我可以从头开始(在你的情况下这可能是不可能的),我会保持 INFORMATION table 原样,我会删除FEED 中的 TOT_REM 列,我将创建一个看起来像当前 FEED table 的视图。您可以在视图定义中编写所有必要的逻辑。

添加:

首先,这是一个视图定义;它假设基数 tables INFORMATIONFEED 如 OP 所述,FEED.

中没有 TOT_REM
create view remaining_balance (id, s_amount, s_date, tot_rem) as
select i.id, f.s_amount, f.s_date, 
       i.tot_amount - nvl(sum(f.s_amount) over (partition by f.id order by f.s_date), 0)
from   information i left outer join feed f
                     on i.id = f.id
;

视图使用外部联接,以包含 INFORMATION table 中没有任何对应行的 FEED 中的 id。 (然后,为了处理 TOT_REM 计算中的空值,我使用 nvl() 函数将 NULL 转换为 0。)

这里是 运行 视图的示例:

 SQL> select * from information;

        ID TOT_AMOUNT
---------- ----------
         1        100
         2         20
         3         50

3 rows selected.

SQL> select * from feed;

        ID   S_AMOUNT S_DATE
---------- ---------- ----------
         1         10 2010-10-10
         1         10 2010-10-13
         1         30 2010-10-17
         1         10 2016-10-20

4 rows selected.

SQL> select * from remaining_balance order by id, s_date;

        ID   S_AMOUNT S_DATE        TOT_REM
---------- ---------- ---------- ----------
         1         10 2010-10-10         90
         1         10 2010-10-13         80
         1         30 2010-10-17         50
         1         10 2016-10-20         40
         2                               20
         3                               50

6 rows selected.

现在,实施复杂约束的一种行之有效的方法是使用物化视图。完全检查约束仅在行级别起作用,当条件涉及多个 table 时不能使用。在当前问题中,检查是针对两个 table,而 TOT_REM 取决于 FEED table 中的其他行 - 因此 FEED table 无论如何都行不通。

物化视图的做法是定义一个像我创建的视图一样的视图,作为物化视图,用refresh fast on commit定义它(这样在基础上进行DML操作后立即检查约束tables),并在物化视图上创建检查约束。在手头的问题中,这将是对 TOT_RM >= 0.

的检查

唉,当视图定义使用分析函数时,refresh fast on commit 是被禁止的(至少在 Oracle 版本 11.2 之前是这样,这是我所拥有的)。我使用了 sum() 函数的解析版本,所以这行不通。

然而,定义不同的物化视图似乎是有意义的,如下所示:

create materialized view remaining_balance (id, tot_rem) as
select i.id, i.tot_amount - f.sum_s_amount
from   information i inner join (select   id, sum(s_amount) as sum_s_amount 
                                 from     feed 
                                 group by id) f
                     on i.id = f.id
;

SQL> select * from remaining_balance;

        ID    TOT_REM
---------- ----------
         1         40

我不再使用外部连接,因为它只用于显示剩余余额。我假设 INFORMATIONTOT_AMOUNT 有一个检查约束以确保它是非负的,并且 FEED 中的 id 是指向 id 中的主键 INFORMATION,因此对于此版本的视图,外部联接没有透露任何其他信息。 (但是,如果需要,可以包含所有 id)。

在这里你应该可以用refresh fast on commit定义视图,并为tot_rem >= 0的效果添加检查约束。 las,我无法测试它;高级复制(需要创建物化视图日志,refresh fast 又需要它)在我拥有的免费 Oracle Express 版本中不是 available/enabled。尝试对此进行试验 - 它可能是您需要的解决方案。祝你好运!