0

I have a table BOOKING(hotelID, roomNo, guestID, startDate, endDate) and I want to create a trigger to do a validation (before insert) by comparing the startDate and endDate to see if the time slot is already been taken by other guests.

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;

But it gives me syntax errors messages which I don't know how to solve (I am new to 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   
Alex Poole
  • 183,384
  • 11
  • 179
  • 318
Hugh H
  • 167
  • 1
  • 3
  • 18

2 Answers2

1

You are incorrectly using the CASE..WHEN structure on PL/SQL. Check the PL/SQL Control Structures manual page on Oracle documentation site. Here is another sample of CASE..WHEN use on a SO question and its answer.

Basically you will need to change this

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

By this:

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;

Or as suggested on a comment to your question, if you are checking just one condition, why not using and IF structure? It will be cleaner and easier to understand/maintain.

Community
  • 1
  • 1
Yaroslav
  • 6,476
  • 10
  • 48
  • 89
  • Thank you for your answer! After I made the change, there are new errors saying that the newTuple needs to be declared. I am just confused with the syntax here... – Hugh H Feb 26 '13 at 15:20
  • @HughH - when you reference the values being inserted, you need to prefix them with a colon; so `:newTuple.hotelID` etc. I've added that to the list of problems in my answer *8-) – Alex Poole Feb 26 '13 at 15:28
1

The immediate problem is that you're using WHEN, which is part of a CASE construct. You would need to use IF here:

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

But that's not how triggers work - you don't insert again from within them, you would be attempting to insert an exact duplicate of the row you're inserting, which would cause your trigger to fire again. Probably only once more - hopefully the second insert would see the first in the cursor and stop. But both would actually do the insert - when there is a conflict with the dates, you aren't actually preventing the insertion, you're just not attempting the duplicate; and the second would still insert, so you'd get two identical rows. The normal way to prevent an insert occurring is for the trigger to raise an exception; conceptually:

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

But this still won't work - you have at least three other problems. First, you can't (easily) query the table you're inserting into; you'll get an ORA-04091 'table is mutating' error. Secondly, when you reference the new values you need to prefix them with a colon, so :newTuple.hotelID etc. And thirdly, you have a concurrency problem; two rows inserting simultaneously with overlapping dates won't see each other and both will succeed. (And not strictly an error, but looping over all records to find a match is going to be inefficient - why not only look for existing rows that conflict with the dates being inserted?)

A trigger doesn't seem to be an appropriate way to enforce this constraint.


OK, this doesn't actually get a mutating table error:

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

Inserting some data:

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.

The two attempts to enter overlapping dates got our -20001 exception, and only the two non-overlapping rows were inserted:

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 

But you still have a concurrency problem, as two sessions could insert overlapping data simultaneously. As they will both be uncommitted, the select count(*) in each trigger instance won't see the other, so they'll both report zero, neither will raise an exception, and both will be inserted.

Alex Poole
  • 183,384
  • 11
  • 179
  • 318
  • Good detailed answer. Indeed there are more errors than just the incorrect `CASE` construction. – Yaroslav Feb 26 '13 at 15:36
  • it is one question of my course project... I am totally new to oracle. (the prof used DB2 in his slides as examples but he recommands using oracle for projects). From my understanding, a trigger is used as validation which is triggered either before or after some execution. In this case, how do I address this problem just to make it work? – Hugh H Feb 26 '13 at 15:40
  • btw, thanks for putting so much efforts on this post. I am really appreciate but your answer seems too low level for me to understand since im a totally noob :) – Hugh H Feb 26 '13 at 15:41
  • @HughH - yes, but you should [look at this](http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#CIHHDEEJ) if you haven't already. Without the background - what you've been taught, and whether you were explicitly told to use triggers - I'm not sure how to steer you. A simple-ish route is an intermediate table that has an entry per day (rather than start and end date range) and room, and a unique constraint on day/room. You could also use a procedure. – Alex Poole Feb 26 '13 at 15:48
  • Thanks for your post Alex. Now I have a clear view of the problem. many thanks! – Hugh H Feb 26 '13 at 20:29