1

好的,我有两个表 - ORDERS 和 ORDERLINES - 它们具有基本相同的问题,每个表都有触发器来解决问题。问题是除了具有表级唯一性的 PK 之外,在一个名为 RECID 的字段上,还有另一个字段 RECNO,它需要与另一个字段的关系是唯一的。

这些表与 FK 相关,如下所示:

ORDERS.WAREHOUSEID > WAREHOUSES.CUSTOMERID > CUSTOMERS

ORDERSLINES.ORDERID > ORDERS

OnORDERS并且ORDERSLINES我有BEFORE INSERT触发器来分配特定于领域的唯一RECNO
ORDERS中,RECNO需要在记录范围内是唯一的CUSTOMERS
ORDERLINES中,RECNO需要在记录范围内是唯一的ORDERS

触发器ORDERS工作得很好。插入新订单时,会为其所属的客户 分配下一个唯一的RECNO 。

ORDERLINES另一方面,应该在它所属的顺序内分配下一个唯一RECNO的触发器会抛出可怕的{ORA-04091: table ORDERLINES is mutating, trigger/function may not see it}异常。

这是有效的触发器:

CREATE OR REPLACE TRIGGER ORDERS_BI 
BEFORE INSERT ON ORDERS 
FOR EACH ROW
DECLARE
    CUSTID  WAREHOUSES.CUSTOMERID%TYPE;
BEGIN
    SELECT MIN(CUSTOMERID) INTO CUSTID FROM WAREHOUSES 
        WHERE NVL(WARE_ID, '-') = NVL(:NEW.WAREHOUSEID, '-');

    SELECT NVL(MAX(RECNO), 0) + 1
        INTO :NEW.RECNO
        FROM deploy.ORDERS O
        LEFT JOIN deploy.WAREHOUSES W
            ON NVL(W.REC, '-') = NVL(O.WAREHOUSEID, '-')
        WHERE NVL(W.CUSTOMERID, '-') = NVL(CUSTID, '-');
END;

这是不起作用的触发器

CREATE OR REPLACE TRIGGER ORDERLINES_BI 
BEFORE INSERT ON ORDERLINES 
FOR EACH ROW
DECLARE
    nORDERID ORDERLINES.ORDERID%TYPE;
BEGIN
    SELECT MIN(ORDERID) INTO nORDERID FROM REVORDERS 
        WHERE ORDERID = :NEW.ORDERID;

    SELECT NVL(MAX(RECNO), 0) + 1
      INTO :NEW.RECNO
      FROM deploy.ORDERLINES L
      LEFT JOIN deploy.ORDERS O
        ON O.ORDERID = L.ORDERID
      WHERE O.ORDERID = nORDERID;
END;

有人可以解释为什么第一个有效,而第二个无效?有什么方法可以重写第二个以使其工作吗?

4

2 回答 2

4

我首先查看了您的代码,而不是您的解释。我的第一个想法是“这个人试图伪造一个序列。” 这显然不是您问题的答案,但这是您首先遇到麻烦的原因。

当您在伪造序列时遇到问题时,显而易见的解决方案是使用真实序列。

正如Nicholas 已经指出的那样,当您尝试从触发触发器的表中读取时,会发生 ORA-04091。有多种方法可以避免这种情况,其中大多数避免尝试做一些有点时髦的事情。但是,它们不会影响错误的根本原因;那是你做错了什么。此错误通常表示以下两种情况中的一种或两种:

  1. 您将太多逻辑放入触发器中
  2. 您的数据模型有缺陷。

第一个解决方案是将逻辑移动到一个包中,这具有去除一层混淆的额外好处。第二个解决方案是正确规范您的数据库。

在您的情况下,根据您提供的信息,您的数据模型似乎没问题,尽管正如我所说,我不同意实施。

这为您提供了四个选项来解决您的问题,我将按顺序详细说明

  1. 删除你的触发器。
  2. 用序列替换您当前的逻辑。
  3. 将所有触发逻辑删除到一个过程中。
  4. 破解你的错误。

我不打算讨论第 3 点,因为你可以自己做。Nicholas 已经部分涵盖了第 4 点,我不会提倡我不同意的内容。这留下了第 1 点和第 2 点。你说

在 ORDERS 中,RECNO 在 CUSTOMERS 记录范围内必须是唯一的。

这不是您实现它的方式。您的代码在记录范围内RECNO 连续CUSTOMERS根据定义ORDERS,两者的主键在记录范围内都是唯一ORDERLINES的。CUSTOMERS

就其本身而言,这意味着选项 1 最适合您。完全移除触发器;表的主键已经在做你需要的一切。这也使选项 2 无效;如果您添加一个序列,那么它将基本上是一个单独的主键。

我没有理由认为您需要在每个客户中连续唯一的订单;为什么要这样做呢?

于 2012-09-09T10:05:59.393 回答
2

您收到该错误是因为第二个触发器在修改表时尝试读取表。当父表上的触发器导致引用外键的子表上的插入时,也会发生这种情况。作为快速解决创建视图并尝试使用而不是触发器的方法。另请查看Tom 的示例,了解如何处理变异问题。此外,如果将第二个触发器保持原样,则任何插入 your_table select .. from table都会引发变异错误。例如:

此插入将起作用

insert into ORDERLINES(column1, column2... columnN) 
  values(val1, val2,..., valN)

但是这个不会。

insert into ORDERLINES(column1, column2... columnN) 
  select val, val..val from table 
于 2012-09-09T06:11:18.597 回答