【问题标题】:Syntax error for a before trigger前触发器的语法错误
【发布时间】:2013-02-26 07:04:32
【问题描述】:

我有一张桌子BOOKING(hotelID, roomNo, guestID, startDate, endDate) 我想通过比较startDateendDate 来创建一个触发器来进行验证(在插入之前),以查看该时隙是否已被其他客人占用。

CREATE OR REPLACE TRIGGER MYTRIGGER 
BEFORE insert ON BOOKING 

referencing new as newTuple
for each row

declare
  t boolean;
  cursor c is 
    select startDate,endDate from ROOM where hotelID = newTuple.hotelID and ROOMNO = newTuple.roomNo;
BEGIN
  t := true;
  open c;
  loop
    fetch c into mStartDate, mEndDate;
    exit when c%NOTFOUND;

    if (NOT((newTuple.startDate >= mEndDate and newTuple.endDate >= mEndDate)
        or(newTuple.startDate <= mStartDate and newTuple.endDate <= mStartDate))) then
      t := false;
    end if;

  end loop;
  close c;

  WHEN (t=true) then
  INSERT INTO BOOKING(hotelID,roomNo,guestID,startDate,endDate) 
  values(newTuple.hotelID, newTuple.roomNo, newTuple.guestID, newTuple.startDate,
         newTyple.endDate);
END;

但它给了我不知道如何解决的语法错误消息(我是 Oracle 新手):

Error(26,3): PLS-00103: Encountered the symbol "WHEN" when expecting one of the following:
( begin case declare end exception exit for goto if loop mod
null pragma raise return select update while with
<an identifier> <a double-quoted delimited-identifier>
<a bind variable> << continue close current delete fetch lock
insert open rollback savepoint set sql execute commit forall
merge pipe purge
The symbol "case" was substituted for "WHEN" to continue. 
Error(30,4): PLS-00103: Encountered the symbol ";" when expecting one of the following:
case   

【问题讨论】:

  • oracle pl/sql中有when构造吗?如果不是,您可能想要使用上面使用的 IF 语句。

标签: sql oracle syntax triggers


【解决方案1】:

直接的问题是您正在使用WHEN,它是CASE 构造的一部分。你需要在这里使用IF

IF t then
    INSERT INTO BOOKING(hotelID,roomNo,guestID,startDate,endDate) ...
END IF;

但这不是触发器的工作方式 - 您不会从其中再次插入,您会尝试插入与您正在插入的行完全相同的副本,这会导致您的触发器再次触发。可能只会再一次 - 希望第二个插入会在光标中看到第一个并停止。但是两者实际上都会插入 - 当日期发生冲突时,您实际上并没有阻止插入,您只是没有尝试重复;第二个仍然会插入,所以你会得到两个相同的行。防止插入发生的正常方法是让触发器引发异常;概念上:

IF NOT t THEN
    RAISE_APPLICATION_ERROR(-20001, 'Overlapping dates');
END IF;

但这仍然行不通-您至少还有三个其他问题。首先,您不能(轻松)查询要插入的表;你会得到一个 ORA-04091 'table is mutating' 错误。其次,当您引用新值时,您需要在它们前面加上冒号,例如:newTuple.hotelID 等。第三,您遇到了并发问题;同时插入具有重叠日期的两行不会互相看到,两者都会成功。 (严格来说不是错误,但循环遍历所有记录以查找匹配项将是低效的 - 为什么不只查找与插入日期冲突的现有行?)

触发器似乎不是强制执行此约束的合适方式。


好的,这实际上并没有出现变异表错误:

create table booking(hotelid number, roomno number, guestid number,
    startdate date, enddate date);

create or replace trigger mytrigger
before insert on booking 
referencing new as new
for each row
declare
    cnt number;
begin
    select count(*) into cnt from booking
    where hotelid = :new.hotelid
    and roomno = :new.roomno
    and not (enddate < :new.startdate or startdate > :new.enddate);

    if cnt > 0 then
        raise_application_error(-20001, 'Overlapping dates');
    end if;
end;
/
TRIGGER MYTRIGGER compiled

插入一些数据:

insert into booking values (1, 1, 1, date '2013-02-28', date '2013-03-05');

1 rows inserted.

insert into booking values (1, 1, 2, date '2013-02-27', date '2013-03-01');

Error starting at line 24 in command:
insert into booking values (1, 1, 2, date '2013-02-27', date '2013-03-01')
Error report:
SQL Error: ORA-20001: Overlapping dates
ORA-06512: at "STACKOVERFLOW.MYTRIGGER", line 10
ORA-04088: error during execution of trigger 'STACKOVERFLOW.MYTRIGGER'

insert into booking values (1, 1, 3, date '2013-03-05', date '2013-03-06');

Error starting at line 25 in command:
insert into booking values (1, 1, 3, date '2013-03-05', date '2013-03-06')
Error report:
SQL Error: ORA-20001: Overlapping dates
ORA-06512: at "STACKOVERFLOW.MYTRIGGER", line 10
ORA-04088: error during execution of trigger 'STACKOVERFLOW.MYTRIGGER'

insert into booking values (1, 1, 4, date '2013-03-06', date '2013-03-07');

1 rows inserted.

两次输入重叠日期的尝试得到了我们的 -20001 异常,并且只插入了两个不重叠的行:

select * from booking;

   HOTELID     ROOMNO    GUESTID STARTDATE  ENDDATE  
---------- ---------- ---------- ---------- ----------
         1          1          1 28/02/2013 05/03/2013 
         1          1          4 06/03/2013 07/03/2013 

但是您仍然遇到并发问题,因为两个会话可能同时插入重叠数据。由于它们都未提交,每个触发器实例中的 select count(*) 不会看到另一个,因此它们都将报告零,也不会引发异常,并且都将被插入。

【讨论】:

  • 很好的详细答案。事实上,除了不正确的CASE 构造之外,还有更多错误。
  • 这是我课程项目的一个问题......我对甲骨文完全陌生。 (教授在他的幻灯片中使用 DB2 作为示例,但他建议将 oracle 用于项目)。据我了解,触发器用作验证,在某些执行之前或之后触发。在这种情况下,我该如何解决这个问题才能让它发挥作用?
  • 顺便说一句,感谢您为这篇文章付出了这么多努力。我真的很感激,但你的回答对我来说似乎太低了,因为我完全是个菜鸟:)
  • @HughH - 是的,但如果你还没有,你应该look at this。没有背景——你被教导了什么,以及你是否被明确告知要使用触发器——我不知道如何引导你。一个简单的路线是一个中间表,它每天有一个条目(而不是开始和结束日期范围)和房间,以及对天/房间的唯一约束。你也可以使用一个过程。
  • 感谢您的帖子,亚历克斯。现在我对这个问题有了清晰的认识。非常感谢!
【解决方案2】:

您在 PL/SQL 上错误地使用了CASE..WHEN 结构。查看 Oracle 文档站点上的 PL/SQL Control Structures 手册页。这是 CASE..WHEN 的另一个示例,用于 SO question 及其答案。

基本上你需要改变它

WHEN (t=true) then
INSERT INTO BOOKING(hotelID,roomNo,guestID,startDate,endDate) 
VALUES (newTuple.hotelID, newTuple.roomNo, newTuple.guestID, newTuple.startDate,
        newTyple.endDate);

通过这个:

CASE
  WHEN (t=true) THEN INSERT INTO BOOKING(hotelID,roomNo,guestID,startDate,endDate) 
                     VALUES (newTuple.hotelID, newTuple.roomNo, newTuple.guestID, 
                             newTuple.startDate, newTyple.endDate);
END CASE;

或者正如对您问题的评论所建议的那样,如果您只检查一个条件,为什么不使用和IF 结构?它会更干净,更容易理解/维护。

【讨论】:

  • 感谢您的回答!在我进行更改后,有新的错误说需要声明 newTuple。我只是对这里的语法感到困惑......
  • @HughH - 当你引用插入的值时,你需要在它们前面加上一个冒号;所以:newTuple.hotelID 等。我已将其添加到我的答案中的问题列表中 *8-)
猜你喜欢
  • 2015-08-09
  • 1970-01-01
  • 1970-01-01
  • 2015-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-20
相关资源
最近更新 更多