3つの値で構成されるテーブルがあります
- 参加者のID
- コースイベントのID
- マーク
それぞれについてcourseevent
、許可されているのは15人だけです
Oracleを使用してこれを確認するにはどうすればよいですか?
この問題の最も重要な側面は、マルチユーザー環境で機能させることです。
Oracle は、READ COMMITTED および SERIALIZED 分離レベルのみを許可します。ファントムまたはダーティ リードはなく、コミットされていないセッションを「覗く」メカニズムもありません。 詳細をご覧ください。
つまり、このステートメント
select courseevent, count(*)
from courseparticpants
group by courseevent;
コミットされたレコードの数が表示されます。レコードを挿入し続ける場合、他の誰かがその間に作業をコミットした場合でも、16 番目の予約を挿入できます。逆に、実際には誰かが行を削除しようとしているときに、コースがすでに満員であると判断する場合があります。
これを制御するには、courseparticpants
一度に 1 つのセッションだけがレコードをテーブルに挿入できるように、テーブルへのアクセスをシリアル化する必要があります。これを行うにはさまざまな方法がありますが、最も安全なのは次のとおりです。
lock table courseparticpants exclusive nowait;
ロックの取得に失敗した場合は、別のセッションが既にそれを処理していることがわかります。それ以外の場合は、カウントを実行し、新しい予約を挿入し、ルールが破られていないという確信を持って必要なことを行うことができます.
明らかな理由から、ロックが多すぎるためにロックをフリーズしないことが重要です。他の誰もテーブルで作業を行うことができません。少し邪魔にならないメカニズムは、親テーブルの関連するレコードをロックすることです。データ モデルについて仮定を立てたくなかったので、最初にこれを提案しませんでした。
select whatever
from courseevents
where courseevent = :p1
for update nowait;
これにより、他のセッションが別のイベントの参加者を予約できるようになります。 詳細をご覧ください。
これらのソリューションはどちらも、トランザクションを管理するために、PL/SQL などでプログラム ユニットを作成する必要があります。
「これを制約で解決する可能性はありますか?」
いいえ、Oracle は CHECK 制約で SQL を許可していません。標準 SQL には ASSERTIONS の概念がありますが、Oracle には実装されていません。
考えられる解決策の 1 つはparticipantid
、 内でカウントを行うcourseevent
ことです。そのため、チェック制約を適用できます。
check ( participantid <= 15)
ただし、現在の参加者数の正確な数字を取得して正しいものにするために、すべてのロックなどを行う必要がありn+1
ます。
通常のテーブル制約では、個々の行のみが分離されて考慮されますが、要件は行のグループをまとめて考慮することです。これは、具体化されたビューの制約を使用して要件を実装するかなり複雑なソリューションです。これは、結果セット内の列に制約を定義するものと考えることができます。
create table course_participants(
course varchar2(20) not null
,participant varchar2(20) not null
,constraint course_participants_pk primary key(course, participant)
);
-- Need this for fast refreshable mview
create materialized view log
on course_participants
with rowid(course, participant)
including new values;
-- A materialized view with a count of participants per course
create materialized view course_parts_max_mv
refresh fast on commit
as
select course
,count(*) as participants
from course_participants
group
by course;
-- This is where you perform the check.
-- I've used 2 participants to make the example easier
alter materialized view course_parts_max_mv
add constraint too_many_participants check(participants <= 2);
上記の DDL は、テーブルとマテリアライズド ビューを作成します。具体化されたビューには、コースごとに 1 行と参加者数が含まれます。秘訣は、ベース テーブルで制約を宣言するのではなく、マテリアライズド ビューで宣言できるようになったことです。
-- One participant is ok!
insert into course_participants values('Oracle', 'Alfred');
commit;
-- Two participants are ok!
insert into course_participants values('Englis speling', 'Benjamin');
insert into course_participants values('Englis speling', 'Charles');
commit;
-- This will fail, because the count(*) for 'Economics' will return 3
insert into course_participants values('Economics', 'Alfred');
insert into course_participants values('Economics', 'Benjamin');
insert into course_participants values('Economics', 'Charles');
commit;
ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (RNBN.TOO_MANY_PARTICIPANTS) violated
トランザクションをコミットするときに制約がチェックされることに注意してください。したがって、最後の例では参加者は登録されません。
これは、15 以上のイベントへの参加者を示しています。
SELECT participant, COUNT(DISTINCT courseevent) F
FROM Table
GROUP BY participant
HAVING COUNT(DISTINCT courseevent) > 15
select count(*)
from blah, blah, blah
既存のレコードの数を示します。
INSERT INTO MyTable(Col1, Col2, Col3)
SELECT 'Val1', 'Val2', 'Val3'
FROM DUAL
WHERE (SELECT COUNT(*) FROM MyTable WHERE condition) < 15;