0

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

描述:

我有三个对象 - 产品、包含、订单。一个产品可以有多个 CONTAINS,一个 ORDER 可以有多个 CONTAINS(基本上它曾经是 PRODUCT 和 ORDER 之间的多对多关系)。

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

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

示例:产品 X 的“值”为 100。产品 Y 的“值”为 200。我们有一个 ORDER O。现在我在产品 X 和 ORDER O 之间创建 CONTAINS,列“数量”为 5。现在触发器应该乘以 5 * 100 并将 ORDER 列“total”更新为 500。然后我在 PRODUCT Y 和 ORDER O 之间创建 CONTAINS,列“amount”为 10。现在触发器应该重新计算 5 * 100 + 10 * 200 并更新“total”列订购 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) into product FROM 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 sql stddev sum variance execute forall merge time timestamp interval date pipe <an交替 10/89 PLS-00103:在预期以下情况之一时遇到符号“)”:。( * @ % & - + ; / at for mod remaining rem <an exponent (**)> and or group have intersect minus order start union where connect || indicator multiset 15/5 PLS-00103:遇到符号“LOOP”预期以下情况之一时:

4

3 回答 3

1

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

现在,到代码本身。你有

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;

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

最后,在不了解表格的情况下很难推荐编码修改。您已经对它们进行了模糊的描述,但最好将所有内容都放在桌面上。请参阅最小可重现示例

于 2021-01-02T14:36:06.173 回答
0

我认为有一个错误:

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

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

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

于 2021-01-02T13:29:37.803 回答
0

通过删除实际上不需要的循环/光标来简化触发器。

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;

请参阅 DB Fiddle 链接以获取解决方案:https ://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=3be867f6ab2e93978ae45a7d305434a1

PS:当触发器中的 DML 调整得不够好时,触发器可能会导致性能瓶颈。建议检查触发器内 SELECT、INSERT、UPDATE 语句的解释计划并根据需要调整它们。如果索引不可用CONTAINS.ORDER_ID_ORDER 和 PRODUCTS.ID_PRODUCT 创建一个将是有益的,但建议咨询 DBA 负责人。

更新: 现在,由于您需要从触发触发器的表中选择,我们必须忍受著名的 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/sql表中进行行操作,并对pl/sql表中的每一行执行语句操作。

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 链接https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=1fb40eef7cf3a647bc5560ed19490240中找到

于 2021-01-02T14:28:33.730 回答