触发选择子记录,将它们的值相乘并更新父记录

Trigger selecting child records, multiplying their values and updating parent record

我是 PL/SQL 新手,我正在为触发器而苦苦挣扎。

描述:

我有三个对象 - PRODUCT、CONTAINS、ORDER。一个产品可以有多个CONTAINS,一个ORDER可以有多个CONTAINS(以前基本上是PRODUCT和ORDER的多对多关系)

每个产品都有一个“值”列,每个包含有一个“金额”列,每个订单都有一个“总计”列。

当我通过创建新的 CONTAINS 将新产品添加到 ORDER 时,我想重新计算 ORDER 上的字段“总计”。

示例:产品 X 的“价值”为 100。产品 Y 的“价值”为 200。我们有一个 ORDER O。现在我在产品 X 和 ORDER O 之间创建 CONTAINS,列“金额”为 5。现在触发器应乘以 5 * 100 并将 ORDER 列“总计”更新为 500。然后我在 PRODUCT Y 和 ORDER O 之间创建 CONTAINS,列“数量”为 10。现在触发器应重新计算 5 * 100 + 10 * 200 并更新ORDER O 上的“总计”列为 2500。

我的错误触发器:

    create or replace TRIGGER TRIGGER1 
AFTER DELETE OR INSERT OR UPDATE OF AMOUNT, PRODUCT_ID_PRODUCT, ORDER_ID_ORDER ON CONTAINS 
REFERENCING NEW AS n
FOR EACH ROW
DECLARE
value number;
amount number;
total number;
BEGIN
LOOP
FOR emp IN (SELECT AMOUNT, PRODUCT_ID_PRODUCT, ORDER_ID_ORDER FROM CONTAINS WHERE ORDER_ID_ORDER = :n.ORDER_ID_ORDER) 
LOOP
(SELECT SUM(VALUE) into product FROM PRODUCT WHERE ID_PRODUCT = :emp.PRODUCT_ID_PRODUCT);
amount:= emp.AMOUNT;
total:= total + (product * amount);
UPDATE ORDER SET ORDER.TOTAL = total WHERE ID_ORDER = :n.ORDER_ID_ORDER;
END LOOP;
END LOOP;
END;

编辑:此处显示错误:

(SELECT SUM(VALUE) 从 PRODUCT WHERE ID_PRODUCT = :emp.PRODUCT_ID_PRODUCT)

说我不能使用“emp”。

EDIT2:错误信息:

10/2 PLS-00103:在预期以下情况之一时遇到符号“SELECT”:( - + case mod new not null continue avg count current exists max min prior sqlstddev sum variance execute forall merge time timestamp interval date pipe and or group having intersect minus order start union where connect || 指标多重集 15/5 PLS-00103:在期望以下之一时遇到符号“LOOP”:;

我认为有误:

SELECT SUM(VALUE) into product FROM PRODUCT WHERE ID_PRODUCT = :emp.PRODUCT_ID_PRODUCT

而不是 :emp。你应该使用 :n.

---更新 删除此 SQL 语句中的括号。你不需要它们 添加变量 PRODUCT 更改变量 TOTAL 的名称,例如 nTotal(它与列名不匹配)

通过删除实际上不需要的 loops/cursors 简化了触发器。

create or replace TRIGGER TRIGGER1 
AFTER DELETE OR INSERT OR UPDATE OF AMOUNT, PRODUCT_ID_PRODUCT, ORDER_ID_ORDER 
ON CONTAINS 
REFERENCING NEW AS n
FOR EACH ROW
DECLARE

lv_total number;

BEGIN

SELECT SUM(prdt.VALUE * :n.amount) into lv_total 
FROM PRODUCT prdt where prdt.ID_PRODUCT = :n.PRODUCT_ID_PRODUCT;


UPDATE ORDERs SET TOTAL = lv_total WHERE ID_ORDER = :n.ORDER_ID_ORDER;

END;

参考数据库Fiddle link解决方案:https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=3be867f6ab2e93978ae45a7d305434a1

PS:触发器在触发器中的DML没有调好时会造成性能瓶颈enough.Recommendation是检查SELECT,INSERT,UPDATE语句的解释计划在触发器中并调整它们,因为 desired.If 索引不适用于 CONTAINS.ORDER_ID_ORDER 和 PRODUCTS.ID_PRODUCT 创建一个将是有益的,但建议咨询负责的 DBA。

更新: 现在,由于您需要从触发触发器的 table Select,我们不得不忍受著名的 Mutating 触发器错误 ORA-04091: table MYTABLE.CONTAINS is mutating, trigger/,幸运的是 Oracle 有一个简单的解决方案,使用 Compound triggerOracle Database 11g Release1 版本开始添加。

有关复合触发器的更多详细信息和技术说明,您可以参考http://stevenfeuersteinonplsql.blogspot.com/2016/12/get-rid-of-mutating-table-trigger.html

触发代码是这样的,ta.da.. 所以我们将行取到一个pl/sqltable进行行操作,并对pl/sqltable.

中的每一行进行语句操作
CREATE OR REPLACE TRIGGER trigger2    
FOR UPDATE OR INSERT ON contains    
COMPOUND TRIGGER     

   TYPE typ_contains IS TABLE OF contains%rowtype  INDEX BY PLS_INTEGER;    
   tab_contains   typ_contains;    
    
   AFTER EACH ROW IS    
   BEGIN  
      tab_contains (tab_contains.COUNT + 1).amount :=    
           :NEW.amount;    
      tab_contains (tab_contains.COUNT).product_id_product := :NEW.product_id_product;
      tab_contains (tab_contains.COUNT).order_id_order := :NEW.order_id_order;
      
   END AFTER EACH ROW;    
    
   AFTER STATEMENT IS    
   lv_total number;
   
   BEGIN        
       
      FOR indx IN 1 .. tab_contains.COUNT    
      LOOP   
      
       SELECT SUM(prdt.VALUE * tab_contains(indx).amount) into lv_total 
       FROM PRODUCT prdt,contains cnts
       where cnts.order_id_order = tab_contains(indx).order_id_order 
       and prdt.id_product = cnts.product_id_product;

      UPDATE ORDERs SET TOTAL = lv_total 
      WHERE ID_ORDER = tab_contains(indx).ORDER_ID_ORDER;
                                     
      END LOOP;    
   END AFTER STATEMENT;    
END trigger2; 
/

可以在 DBfiddle 中找到更新的解决方案 link https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=1fb40eef7cf3a647bc5560ed19490240

你有几个问题,但最根本的一个是你根本不应该这样做。试图存储 - 并保持同步 - 始终可以计算的值是一个基本的设计缺陷。

现在,到代码本身。 你有

SELECT SUM(VALUE) into product

您的 INTO 目标必须是已声明的变量。看起来您正在尝试 SELECT .. INTO 列名称。

您应该命名局部变量以区分它们和列名。因此,而不是

DECLARE
value number;
amount number;
total number;

你应该

DECLARE
v_value number;
v_amount number;
v_total number;

相反,您应该考虑 table 和列的标准命名约定。对于列,我使用并推荐 形式的名称,因此 ORDER_ID、PRODUCT_NAME 等。PRODUCT_ID_PRODUCT、ORDER_ID_ORDER 是什么?在列名中重复 table 名称通常没有好处。尽管有时它是有意义的,因为它仍然遵循 adjective_noun 格式,例如 ORDERS table 的 id 列被命名为 ORDER_ID。还要考虑 table 名称,我通常将 table 名称设为复数名词,因为 tables 跟踪某个实体的多个实例。如果 table 名称对列名有意义(如 ORDER_ID),它将是单数,因为单个行跟踪实体的单个实例。

最后,在不了解 table 的情况下很难推荐编码修改。您已经对它们进行了模糊的描述,但最好将所有内容都放在 table 上。参见 minimal-reproducible-example