我有一个由三个值组成的表
- 参与者ID
- 课程事件ID
- 标记
每人courseevent
只限15人
如何使用 Oracle 进行检查?
这个问题最重要的方面是让它在多用户环境中工作。
Oracle 只允许 READ COMMITTED 和 SERIALIZED 隔离级别。没有幻读或脏读,也没有“偷看”未提交会话的机制。 了解更多。
这意味着这个声明
select courseevent, count(*)
from courseparticpants
group by courseevent;
将显示已提交的记录数。如果您继续插入记录,您仍然可以插入第十六次预订,如果其他人在此期间提交了他们的工作。相反,当实际上有人要删除一行时,您可能会认为课程已经满了。
要控制这一点,您需要序列化对courseparticpants
表的访问,以便一次只有一个会话可以将记录插入其中。有多种方法可以做到这一点,但最安全的是:
lock table courseparticpants exclusive nowait;
如果您未能获得锁定,您就知道另一个会话已经在处理它。否则,您可以运行您的计数,插入一个新的预订并执行任何其他需要的操作,并确信您的规则没有被违反。
重要的是不要因为太锁而冻结在锁上,原因很明显:没有其他人可以在桌子上做他们的工作。一个稍微不那么突兀的机制是锁定父表中的相关记录;我没有首先提出这个建议,因为我不想对您的数据模型做出假设。
select whatever
from courseevents
where courseevent = :p1
for update nowait;
这将允许其他会议为另一个活动预订参与者。 了解更多。
这两种解决方案都需要编写一个程序单元——比如在 PL/SQL 中——来管理事务。
“有没有可能在约束条件下解决这个问题?”
不,Oracle 不允许在其 CHECK 约束中使用 SQL。标准 SQL 有断言的概念,但 Oracle 没有实现它们。
一种可能的解决方案是在participantid
内进行计数courseevent
,因此您可以强制执行检查约束
check ( participantid <= 15)
但是,您仍然需要执行所有锁定操作才能获得当前参与者数量的准确数字,以确保您n+1
是正确的。
select count(*)
from blah, blah, blah
为您提供现有记录的数量。
常规表约束只考虑单独的行,但您的要求是一起考虑一组行。这是一个相当复杂的解决方案,它使用物化视图约束来实现需求。您可以将其视为在结果集中的列上定义约束。
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 创建了一个表和一个物化视图。物化视图将包含每门课程的一行以及参与者的 nr。诀窍是,我们现在可以在物化视图上声明它,而不是在基表上声明约束。
-- 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
INSERT INTO MyTable(Col1, Col2, Col3)
SELECT 'Val1', 'Val2', 'Val3'
FROM DUAL
WHERE (SELECT COUNT(*) FROM MyTable WHERE condition) < 15;