0

我有一个表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   
4

2 回答 2

1

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

基本上你需要改变这个

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结构?它将更干净,更容易理解/维护。

于 2013-02-26T07:57:43.000 回答
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(*)每个触发器实例中的 都不会看到另一个,因此它们都将报告零,都不会引发异常,并且都将被插入。

于 2013-02-26T08:19:14.443 回答