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 制御構造のマニュアル ページを確認してください。SO の質問とその回答に対する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;

または、質問へのコメントで提案されているように、条件を 1 つだけチェックする場合は、andIF構造を使用してみませんか? よりクリーンで理解しやすく、維持しやすくなります。

于 2013-02-26T07:57:43.000 に答える
1

当面の問題は、構造WHENの一部である を使用していることですCASE。ここで使用する必要がありIFます:

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

しかし、それはトリガーの仕組みではありません-トリガー内から再度挿入しないと、挿入している行の正確な複製を挿入しようとするため、トリガーが再び起動します。おそらくもう一度だけです-うまくいけば、2番目の挿入はカーソル内の最初の挿入を見て停止します。しかし、どちらも実際には挿入を行います - 日付に競合がある場合、実際には挿入を妨げているわけではなく、複製を試みていないだけです。2番目はまだ挿入されるため、2つの同一の行が得られます。挿入の発生を防ぐ通常の方法は、トリガーで例外を発生させることです。概念的に:

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

しかし、これでもうまくいきません。他に少なくとも 3 つの問題があります。まず、挿入先のテーブルに対して (簡単に) クエリを実行できません。ORA-04091 'table is mutating' エラーが発生します。第 2 に、新しい値を参照するときに、コロンなどのプレフィックスを付ける必要があります:newTuple.hotelID。第 3 に、同時実行の問題があります。重複する日付で同時に挿入する 2 つの行は、互いに認識されず、両方とも成功します。(厳密にはエラーではありませんが、すべてのレコードをループして一致を見つけるのは非効率的です。挿入される日付と競合する既存の行を探すだけではどうですか?)

トリガーは、この制約を強制する適切な方法ではないようです。


OK、これは実際には変更テーブル エラーにはなりません。

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.

重複する日付を 2 回入力しようとすると、-20001 例外が発生し、重複しない 2 つの行のみが挿入されました。

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 

ただし、2 つのセッションが重複するデータを同時に挿入する可能性があるため、同時実行性の問題は依然として存在します。どちらもコミットされないためselect count(*)、各トリガー インスタンスは他方を認識しないため、どちらもゼロを報告し、どちらも例外を発生させず、両方が挿入されます。

于 2013-02-26T08:19:14.443 に答える