0

私はオラクルでさまざまな制約を使用してトリガー機能を実験してきましたが、最近、誰かが次の条件でトリガーの代わりにマテリアライズドビューを使用することをお勧めします。これは非常に賢明な選択だと思います。しかし、学習目的のために、トリガー機能がどのように機能するかを知りたいです。

月単位で特定の制約ベースをチェックするトリガーを作成します。

テーブルレンタル

|ID|Member|book|
----------------------
1  | John |fairytale|2-jun-12| 
2  | Peter |friction|4-jun-12|  
3  | John |comic|12-jun-12|  
4  | Peter |magazine|20-jun-12|  
5  | Peter |magazine|20-jul-12|  
6  | Peter |magazine|20-jul-12|  

制約 : メンバーは毎月 2 冊の本しか借りることができません。

私がよく理解していない@HiltoNによって提供されたコード:

create or replace trigger tr_rent
  before insert on rent
  for each row 
declare
  v_count number;
begin
  select count(id) 
    into v_count
    from rent 
   where member = :new.member;

  if v_count > 2 then 
    raise_application_error (-20001, 'Limit reached'); 
  end if;
end;
4

2 に答える 2

1

一般に、そのトリガーは機能しません。

一般に、テーブル X の行レベル トリガーはテーブル X に対してクエリを実行できません。したがって、この場合、行レベル トリガーRENTは通常、テーブルのクエリを実行できません。これRENTを行うと、変更トリガー例外がスローされます。アプリケーションがステートメントを使用して一度に 1 行しか挿入しないことを保証したい場合はINSERT ... VALUES、ミューティング トリガー エラーに遭遇することはありませんが、これは一般的に適切な制限ではありません。また、マルチユーザー環境では適切ではありません。ほぼ同時に実行されている 2 つのトランザクションが両方とも同じユーザーに対して書籍をチェックアウトしている場合、このトリガーにより、両方のトランザクションが成功する可能性があります。

この種のチェックを追加する適切な場所は、ほぼ確実に、RENTレコードを作成するストアド プロシージャです。そのストアド プロシージャは、メンバーが当月に所有しているレンタル数をチェックし、それが制限を超えている場合はエラーを出力する必要があります。何かのようなもの

CREATE OR REPLACE PROCEDURE rent_book( p_member IN rent.member%type,
                                       p_book   IN rent.book%type )
AS
  l_max_rentals_per_month constant number := 2;

  type rental_nt is table of rent.rend_id%type;
  l_rentals_this_month             rental_nt;

BEGIN
  SELECT rent_id
    BULK COLLECT INTO l_rentals_this_month
    FROM rent
   WHERE member = p_member
     AND trunc(rental_date,'MM') = trunc(sysdate, 'MM')
     FOR UPDATE;

  IF( l_rentals_this_month.count > l_max_rentals_per_month )
  THEN
    RAISE_APPLICATION_ERROR( -20001, 'Rental limit exceeded' );
  ELSE
    INSERT INTO rent( rent_id, member, book, rental_date )
      VALUES( rent_id_seq.nextval, p_member, p_book, sysdate );
  END IF;
END;

トリガーを使用してこのようなことを本当に強制したい場合、ソリューションははるかに複雑になります。効率を気にしない場合は、ステートメントレベルのトリガーを作成できます

create or replace trigger tr_rent
  after insert on rent
declare
  v_count number;
begin
  select count(id) 
    into v_count
    from (select member, count(*)
            from rent
           where trunc(rental_date,'MM') = trunc(sysdate,'MM')
           group by member
          having count(*) > 2); 

  if v_count >= 1 then 
    raise_application_error (-20001, 'At least one person has exceeded their rental limit'); 
  end if;
end;

これは機能しますが、毎回すべてのメンバーに対して検証を行う必要があります (少なくとも)。多数のメンバーがいる場合、これはかなり非効率的です。複雑さを大幅に高めることで、ワークロードを削減できます。もし、あんたが

  1. のコレクションであるパッケージ グローバル変数を宣言するパッケージを作成しますrent.member%type
  2. このコレクションを初期化する before ステートメント トリガーを作成します。
  3. :new.memberこのコレクションにを追加する行レベルのトリガーを作成します
  4. 上記と同様の after ステートメント トリガーを作成しますが、member保持しているコレクション内にあるという追加の条件があります。

この「3 トリガー ソリューション」は、特に適切なソリューションが最初にトリガーを使用しない場合に、システムにかなりの複雑さを追加します。

于 2012-11-13T19:31:59.013 に答える
0

ジャスティンに同意します。あなたのトリガーはいくつかの理由で機能しません。マテリアライズド ビューまたはストアド プロシージャ ソリューションを使用すると、そこにたどり着くことができます。この問題に対する最善の解決策は、単純な一意のインデックスになることをお勧めします。

create unique index rent_user_limit on rent (member, trunc(rental_date, 'month'));

于 2012-11-13T19:40:56.803 に答える