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
及时调用合并语句驻留在触发器内,或者您可以使用相同的调整方法。
好的,我从 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
及时调用合并语句驻留在触发器内,或者您可以使用相同的调整方法。