PL / SQL触发器返回缺货错误:使用编译错误创建触发器

时间:2014-09-26 13:12:28

标签: sql oracle plsql triggers

我有以下表格:

order_lines table
    Name  Null?  Type  
    ORDER_ID  NOT NULL  NUMBER(5)  
    PRODUCT_ID  NOT NULL  NUMBER(4)  
    ACTUAL_PRICE     NUMBER  
    DISCOUNT     NUMBER  
    QUANTITY     NUMBER  
    TOTAL_AMOUNT     NUMBER  

Product_inventory table
Name  Null?  Type  
PRODUCT_ID  NOT NULL  NUMBER(4)  
QTY_ON_HAND     NUMBER  
QTY_ON_ORDER     NUMBER  
DATE_ORDERED     DATE  
DELIVERY_DATE     DATE 

我正在尝试使用触发器检查订单数量并查看订单是否缺货,并显示消息(如果有)。我当前的触发器正在编译时出现一些错误:

    CREATE OR REPLACE TRIGGER check_order_line  
  BEFORE INSERT OR UPDATE ON order_lines
  for each row
DECLARE
  l_current_stock product_inventory.qty_on_hand%type;
BEGIN 
  select product_inventory.qty_on_hand
    into l_current_stock
    from product_inventory, order_lines
   where product_inventory.product_id = :new.product_id;
  if(:new.quantity > l_current_stock) then
    RAISE_APPLICATION_ERROR(-20103, 'Insufficient Stock');
  else
    update product_inventory
   set qty_on_hand = qty_on_hand - :new.quantity
 where product_inventory.product_id = :new.product_id;
  end if;
END;  

当我测试触发器时,我遇到以下错误:

 insert into order_lines values (388,1023,100,20,2,160)
            *

ERROR at line 1: 
ORA-01422: exact fetch returns more than requested number of rows 
ORA-06512: at "DBA643.CHECK_ORDER_LINE", line 4 
ORA-04088: error during execution of trigger 'DBA643.CHECK_ORDER_LINE' 

为什么我会这样做?

2 个答案:

答案 0 :(得分:3)

SQL 中的赋值运算符为=,而不是:=(仅适用于PL / SQL)。此外,比较运算符也是=而不是:=

所以这句话:

update product_inventory
   set qty_on_hand:= qty_on_hand - :new.quantity
 where product_id:= :new.product_id;

应该是

update product_inventory
   set qty_on_hand = qty_on_hand - :new.quantity
 where product_id = :new.product_id;

答案 1 :(得分:0)

您的触发器不是多用户安全的。由于用户可以从表中进行选择而不看其他用户未提交的更改,他们可能认为有足够的库存实际上,当其他用户提交他们的更改时,将不会。这可能会导致qty_on_hand变为负面。您需要进行检查并更新原子过程。

此外,您没有在更新中正确处理数量的更改,并且没有考虑如果删除order_lines会发生什么(尽管可能有其他验证可以阻止此情况),因此数量是除去。

一个选项是SELECT...FOR UPDATE,以阻止其他用户查看当前总数,直到该用户完成并提交/回滚任何更改,例如。

CREATE OR REPLACE TRIGGER check_order_line  
  BEFORE INSERT OR UPDATE OR DELETE ON order_lines
  FOR EACH ROW
DECLARE
  l_quantity_change product_inventory.qty_on_hand%TYPE;
  l_current_stock   product_inventory.qty_on_hand%TYPE;
BEGIN 
  IF INSERTING THEN
    l_quantity_change := :new.quantity;
  ELSIF UPDATING THEN
    l_quantity_change :=
      CASE
        WHEN :new.quantity IS NOT NULL AND :old.quantity IS NOT NULL THEN
          :new.quantity - :old.quantity
        WHEN :new.quantity IS NOT NULL THEN
          :new.quantity
        WHEN :old.quantity IS NOT NULL THEN
          -:old.quantity
        ELSE
          NULL
      END;
  ELSIF DELETING THEN
    l_quantity_change := -:old.quantity;
  END IF;
  IF l_quantity_change IS NOT NULL THEN
    SELECT qty_on_hand
      INTO l_current_stock
      FROM product_inventory
     WHERE product_id = :new.product_id
       FOR UPDATE OF qty_on_hand;
    IF l_current_stock IS NOT NULL THEN
      IF l_quantity_change > l_current_stock THEN
        raise_application_error(-20103, 'Insufficient Stock');
      ELSE
        UPDATE product_inventory
           SET qty_on_hand = qty_on_hand - l_quantity_change
         WHERE product_id = :new.product_id;
      END IF;
    END IF;
  END IF;
END;  

另一种选择是向product_inventory表添加检查约束,以确保qty_on_hand不能为负数。如果值小于零,这将导致检查约束违规错误,例如

ALTER TABLE product_inventory
  ADD CONSTRAINT check_qty_on_hand
  CHECK (qty_on_hand IS NULL OR qty_on_hand >= 0)

CREATE OR REPLACE TRIGGER check_order_line  
  BEFORE INSERT OR UPDATE OR DELETE ON order_lines
  FOR EACH ROW
DECLARE
  l_quantity_change product_inventory.qty_on_hand%TYPE;
BEGIN 
  IF INSERTING THEN
    l_quantity_change := :new.quantity;
  ELSIF UPDATING THEN
    l_quantity_change :=
      CASE
        WHEN :new.quantity IS NOT NULL AND :old.quantity IS NOT NULL THEN
          :new.quantity - :old.quantity
        WHEN :new.quantity IS NOT NULL THEN
          :new.quantity
        WHEN :old.quantity IS NOT NULL THEN
          -:old.quantity
        ELSE
          NULL
      END;
  ELSIF DELETING THEN
    l_quantity_change := -:old.quantity;
  END IF;
  IF l_quantity_change IS NOT NULL THEN
    UPDATE product_inventory
       SET qty_on_hand = qty_on_hand - l_quantity_change
     WHERE product_id = :new.product_id
       AND qty_on_hand IS NOT NULL;
  END IF;
END;