before触发器的语法错误

时间:2013-02-26 07:04:32

标签: sql oracle syntax triggers

我有一张桌子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   

2 个答案:

答案 0 :(得分:1)

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

基本上你需要改变这个

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结构?理解/维护起来会更清晰,更容易。

答案 1 :(得分: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'表正在改变'错误。其次,当你引用新值时,你需要用冒号作为前缀,所以: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(*)将看不到另一个,因此它们都将报告零,既不会引发异常,也会插入两者。