ORACLE SQL - 无法让触发器从正在更新的 table 中获取 Sum/Count 个值

ORACLE SQL - Can't get Trigger to obtain Sum/Count of values from that table that is being updated

好的,我从 class 那里得到了一些练习题,但我发现了一个我无法理解的练习题,尽管它听起来很简单。我有这个 tables。我想创建一个触发器,每次我更新或插入一个值到产品 table 后,它都会更新 class table 以及属于该产品的数量 class,以及价格总和。

起初我尝试使用 FOR EACH ROW,但我不知道如何使它读取新条目 class 的产品 table 新产品值没有 'mutating',因为我试图读取正在 updates/inserted 中的产品 table。即使有正确的设置方法也无法弄清楚:new。和:旧。以此目的。

尝试了检查产品 table 并选择变量进行计数和求和的游标,但这是同一个问题,整个 'mutating' 问题。

现在我得到了没有 FOR EACH ROW 的结果,但它计算并汇总了所有 class 的所有产品和价格。我不知道如何让它分别对每个 class 起作用。我虽然可能是一个循环和一个基于最大值 class 增加的计数器,但这似乎过于复杂。任何帮助将不胜感激。

drop table class        cascade constraints;
drop table provider     cascade constraints;
drop table product      cascade constraints;


CREATE TABLE class(
class           number(5)       constraint  pk_class primary key,
description     varchar2(20)    constraint  nn1_class CHECK(description = INITCAP(description) AND description IS NOT NULL),
tot_product     number(5)       constraint  nn2_class CHECK (tot_product >=0  AND tot_product IS NOT NULL),
tot_price       number(12,2)    constraint  nn3_class CHECK (tot_price >=0  AND tot_price IS NOT NULL),
constraint      pk1_class       CHECK (class >=0)
);


INSERT INTO class VALUES(1,'Description 1', 0, 0);
INSERT INTO class VALUES(2,'Description 2', 0, 0);
INSERT INTO class VALUES(3,'Description 3', 0, 0);
INSERT INTO class VALUES(4,'Description 4', 0, 0);
INSERT INTO class VALUES(5,'Description 5', 0, 0);


CREATE TABLE provider(
provider    number(5)       constraint  pk_provider primary key,
description varchar2(20)    constraint  nn1_provider    CHECK(description = INITCAP(description) AND description IS NOT NULL),
constraint  pk1_provider    CHECK (provider >=0)
);

INSERT INTO provider VALUES(1,'Description 1');
INSERT INTO provider VALUES(2,'Description 2');
INSERT INTO provider VALUES(3,'Description 3');
INSERT INTO provider VALUES(4,'Description 4');
INSERT INTO provider VALUES(5,'Description 5');


CREATE TABLE product(
product         number(5)       constraint  pk_product      primary key,
description     varchar2(20)    constraint  nn1_product CHECK (description = INITCAP(description) AND description IS NOT NULL),
price           number(12,2)    constraint  nn2_product CHECK (price >=0        AND price   IS NOT NULL),
available       number(5)       constraint  nn3_product CHECK (available >=0    AND available IS NOT NULL),
class           number(5)       constraint  fk1_product     references class        NOT NULL,           
provider        number(5)       constraint  fk2_product references provider NOT NULL,   
constraint      pk1_product CHECK (product >=0)
);




CREATE OR REPLACE TRIGGER tot_class
AFTER INSERT OR UPDATE ON product

DECLARE
e_tot_product   number(5);  
e_tot_price     number(12,2);

BEGIN

SELECT COUNT(product) INTO e_tot_product
    FROM product
    WHERE class = class; 

SELECT SUM(price) INTO e_tot_price 
    FROM product
    WHERE class = class;


UPDATE class SET tot_product = e_tot_product, tot_price= e_tot_price WHERE class = class;
END;
/


INSERT INTO product VALUES(1,'Description 1', 100, 10, 1, 1);
INSERT INTO product VALUES(2,'Description 2', 100, 10, 1, 2);
INSERT INTO product VALUES(3,'Description 3', 100, 10, 2, 1);
INSERT INTO product VALUES(4,'Description 4', 100, 10, 4, 5);
INSERT INTO product VALUES(5,'Description 5', 100, 10, 2, 3);

所以,你发现了突变。首先,如果您发现需要处理突变,问问自己为什么。我怀疑数据模型或应用程序设计存在缺陷。然而,话虽如此,让我们谈谈变异以及如何避免它。

当你有一个行级触发器并且你在触发器代码中引用构建触发器的 table 时,就会发生突变,并且单个 SQL 语句会影响更多比 table 中的一行多。 Oracle 不知道如何保持一致性,因为每一行都会重新执行触发器。

处理变异的标准方法是写一个包,包含三个函数和一个包级数组。这三个函数分别是initialize()、save_row()和process()。

您需要三个步骤才能使其正常工作。首先,您需要一个语句级别的 BEFORE 触发器来调用 initialize() 函数,以初始化包级别数组。然后行级 BEFORE 触发器将调用 save_row() 函数,该函数将当前行(pk 或 rowid)保存到数组中,最后是语句级 AFTER 触发器调用 process() 来读取行在数组中并相应地处理数据。

如果您转到 http://asktom.oracle.com/ 并搜索 'table mutation',您会找到大量已回答的问题,并附有示例代码。

我建议在触发器中使用 MERGE 语句,如下所示

CREATE OR REPLACE TRIGGER TOT_CLASS
AFTER INSERT OR UPDATE
ON PRODUCT 
REFERENCING NEW AS NEW OLD AS OLD
BEGIN

MERGE INTO class oldclass
using 
( 
SELECT a.class,COUNT(b.product) tot_poduct,sum(b.price) tot_price
    FROM product b
    join
    class a
    on a.class = b.class
    group by a.class
)    newclass
on (oldclass.class = newclass.class)
when matched then 
update set oldclass.tot_product = newclass.tot_poduct,
oldclass.tot_price = newclass.tot_price;

END;
/

此处如果产品 class 更新,我们还必须重新检查旧产品数量和总价。因此上面的语句会出现这样的问题。我仍然不确定在大数据集上的性能。我建议使用 DBMS_JOBS 及时调用合并语句驻留在触发器内,或者您可以使用相同的调整方法。