【问题标题】:PLSQL: syntax problems with triggerPLSQL:触发器的语法问题
【发布时间】:2014-03-11 19:28:11
【问题描述】:

我已经创建了很多类似的触发器,因此我习惯于发现常见的 sntax 错误 - 例如,忘记 END IF 或 THEN。我无法编译这个

CREATE OR REPLACE TRIGGER SOLD
BEFORE UPDATE OF STATUS ON STORE_COPY
FOR EACH ROW
DECLARE 
RENT_OR_SALE NVARCHAR2(6);
UPDATED_DVD_ID NUMBER(10);
BEGIN
UPDATED_DVD_ID := SELECT DVD_ID FROM STORE_COPY WHERE :NEW.STATUS != :OLD.STATUS;
RENT_OR_SALE := SELECT (SELECT RENT_OR_SALE FROM DVD JOIN STORE_COPY ON STORE_COPY.DVD_ID =     DVD.DVD_ID WHERE :NEW.STATUS != :OLD.STATUS) INTO RENT_OR_SALE FROM DUAL;
IF :NEW.STATUS != :OLD.STATUS
THEN
IF :OLD.STATUS = 'Y'
THEN
IF :NEW.STATUS = 'N'
THEN
IF RENT_OR_SALE = 'S'
THEN
INSERT INTO SOLD VALUES(NULL, CURRENT_TIMESTAMP, (SELECT PRICE FROM DVD JOIN STORE_COPY ON       STORE_COPY.DVD_ID = DVD.DVD_ID WHERE :NEW.DVD_IS = UPDATED_DVD_ID), :NEW.DVD_ID);
END IF;
END IF;
END IF;
END IF;
END;
/

终端通过“显示错误”命令向我提供以下信息:

Warning: Trigger created with compilation errors.

SQL> show errors;
LINE/COL ERROR
-------- -----------------------------------------------------------------
5/23     PLS-00103: Encountered the symbol "SELECT" when expecting one of
 the following:
 ( - + case mod new not null <an identifier>
 <a double-quoted delimited-identifier> <a bind variable>
 continue avg count current exists max min prior sql stddev
 sum variance execute forall merge time timestamp interval
 date <a string literal with character set specification>
 <a number> <a single-quoted SQL string> pipe
 <an alternatively-quoted string literal with character set
 specification>
 <an alternat

LINE/COL ERROR
-------- -----------------------------------------------------------------

6/5  PLS-00103: Encountered the symbol "RENT_OR_SALE" 

【问题讨论】:

    标签: oracle plsql triggers syntax-error


    【解决方案1】:

    您不能在 IF 检查条件中使用“select”子句。使用 select INTO 变量,然后在 IF() 中检查该变量。参见示例:if (select count(column) from table) > 0 then

    【讨论】:

    • 显示的“显示错误”实际上是针对上面的代码(已编辑)。我第一次不小心发布了一个早期版本。对不起。没有你说的问题,但是还是不行。
    【解决方案2】:

    您需要使用SELECT...INTO...。 PL/SQL 中没有variable := SELECT...。一个起点是:

    CREATE OR REPLACE TRIGGER SOLD
      BEFORE UPDATE OF STATUS ON STORE_COPY
      FOR EACH ROW
    DECLARE 
      strRENT_OR_SALE NVARCHAR2(6);
      nUPDATED_DVD_ID NUMBER(10);
    BEGIN
      SELECT DVD_ID
        INTO nUPDATED_DVD_ID
        FROM STORE_COPY
        WHERE :NEW.STATUS != :OLD.STATUS;
    
      SELECT RENT_OR_SALE
        INTO strRENT_OR_SALE
        FROM DVD
        JOIN STORE_COPY
          ON STORE_COPY.DVD_ID = DVD_ID
        WHERE :NEW.STATUS != :OLD.STATUS;
    
      IF :NEW.STATUS != :OLD.STATUS THEN
        IF :OLD.STATUS = 'Y' THEN
          IF :NEW.STATUS = 'N' THEN
            IF RENT_OR_SALE = 'S' THEN
              INSERT INTO SOLD VALUES
                (NULL,
                 CURRENT_TIMESTAMP,
                 (SELECT PRICE
                    FROM DVD
                    JOIN STORE_COPY
                      ON STORE_COPY.DVD_ID = DVD.DVD_ID
                    WHERE :NEW.DVD_IS = UPDATED_DVD_ID),
                 :NEW.DVD_ID);
            END IF;
          END IF;
        END IF;
      END IF;
    END;
    

    但即便如此,您仍然会遇到错误。我怀疑那些 SELECT 是单例的(也就是说,它们可能返回不止一行),所以它们会在运行时进行调整。即使你完成了那个,你仍然会得到一个 ORA-04091 错误(表正在变异;触发器看不到它),因为你在这个触发器中有 SELECT 语句读取触发器所在的表开。

    您试图在那些SELECT FROM STORE_COPY... 语句中读取的值是否应该与刚刚修改的相同?如果是这样,它们可用作 :OLD 和 :NEW 值,因此以下可能有效:

    CREATE OR REPLACE TRIGGER SOLD
      BEFORE UPDATE OF STATUS ON STORE_COPY
      FOR EACH ROW
    DECLARE 
      strRENT_OR_SALE NVARCHAR2(6);
      nPrice          NUMBER;
    BEGIN
      SELECT RENT_OR_SALE,
             PRICE
        INTO strRENT_OR_SALE,
             nPrice
        FROM DVD d
        WHERE d.DVD_ID = :NEW.DVD_ID
    
      IF :OLD.STATUS = 'Y' AND
         :NEW.STATUS = 'N' AND
         strRENT_OR_SALE = 'S'
      THEN
        INSERT INTO SOLD VALUES
          (NULL,
           CURRENT_TIMESTAMP,
           nPrice);
      END IF;
    END TRIGGER_SOLD;
    

    我可能猜错了某些字段的位置(例如,我猜到 PRICE 在 DVD 上。如果它实际上在 STORE_COPY 上,那么您可能可以改用 :NEW.PRICE)。

    分享和享受。

    【讨论】:

    • 我没有完全理解你的代码,但它可以工作!我不明白这行代码如何不返回多行:WHERE d.DVD_ID = :NEW.DVD_ID 因为我的 store_copy 表有重复的 dvd_id。在评估 :NEW.DVD_ID 时如何选择正确的行(更新的行)?
    • 如果您注意到,该语句正在读取 DVD 表,其中(我猜)DVD_ID 是唯一的。此触发器对 STORE_COPY 中更新的每一行运行一次,因此 :NEW 值都引用更新的行,因此一次只有一个 :NEW.DVD_ID。如果您更新 STORE_COPY 中的 10 行,此触发器将被调用 10 次,每更新一次 STORE_COPY 行。我希望这会有所帮助。
    【解决方案3】:

    所以删除“;”来自:

    :NEW.STATUS != :OLD.STATUS;),
    

    【讨论】:

    • 我再次编辑了代码以反映您的建议。谢谢。只剩下两个错误。更好:-)
    • 这些行是主要的罪犯: SELECT (SELECT DVD_ID FROM STORE_COPY WHERE :NEW.STATUS != :OLD.STATUS) INTO UPDATED_DVD_ID FROM DUAL; SELECT (SELECT RENT_OR_SALE FROM DVD JOIN STORE_COPY ON STORE_COPY.DVD_ID = DVD.DVD_ID WHERE DVD.DVD_ID = UPDATED_DVD_ID) INTO RENT_OR_SALE FROM DUAL;
    • 我觉得有问题,但我调查它为时已晚 ;-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多