我正在尝试编写一个触发器,禁止医院中的任何房间提供超过3项服务。 RoomServices表具有房间号和服务。因此,确定这一点的唯一方法是按房间分组房间并计算服务。我试过了代码:
CREATE TRIGGER RoomServiceLimit
BEFORE INSERT OR UPDATE ON RoomServices
FOR EACH ROW
DECLARE
numService NUMBER;
CURSOR C1 IS SELECT count(*) AS RoomCount FROM RoomServices WHERE roomNumber = :new.roomNumber;
BEGIN
IF(inserting) THEN
SELECT count(*) into numService FROM RoomServices WHERE roomNumber = :new.roomNumber;
if(numService > 2) THEN
RAISE_APPLICATION_ERROR(-20001,'Room ' || :new.roomNumber || ' will have more than 3 services.');
END IF;
END IF;
IF(updating) THEN
FOR rec IN C1 LOOP
IF(rec.RoomCount > 2) THEN
RAISE_APPLICATION_ERROR(-20001,'Room ' || :new.roomNumber || ' will have more than 3 services.');
END IF;
END LOOP;
END IF;
END;
/
我尝试使用insert和update分别运行每个方法,并且插入始终工作和更新将始终给我变异表错误。我不知道如何解决这个问题,所以任何建议都会非常感激。
谢谢!
答案 0 :(得分:1)
没有可靠的方法来使用触发器强制执行此类约束。一个可能的应用是使用在提交时自动刷新的物化视图,并具有强制执行业务规则的检查约束:
create table roomservices (
pk number not null primary key,
roomnumber number);
create materialized view mv_roomservices
refresh on commit as
select
pk,
roomnumber,
count(*) over (partition by roomnumber) as cnt
from roomservices;
alter table mv_roomservices add constraint
chk_max_2_services_per_room check (cnt <= 2);
现在,每当您为一个房间添加两个以上的服务并尝试提交您的事务时,您将获得ORA-12008异常(物化视图刷新路径中的错误)。
答案 1 :(得分:0)
如果没有一些解决方法,似乎无法解决此问题。如果你找不到更好的东西,请查看:
我猜你有桌子室,否则创建一个:
alter table Room add (
servicesCount integer default 0 not null check (servicesCount <= 3)
);
然后用当前值更新这个数字(不确定该语句是否有效,这不是关键点)
update Room r
set servicesCount = (select count(*)
from RoomServices s
where r.roomNumber = s.roomNumber);
然后在你的触发器中
create trigger RoomServiceLimit
before insert or update on RoomServices
for each row
begin
update Room
set servicesCount = servicesCount + 1
where roomNumber = :new.roomNumber;
end;
看起来很难看,但是,正如我所说,我不确定你能找到更好的触发器。
修改强> 完整的工作示例
drop table Room;
drop table RoomServices;
create table Room (
roomNumber integer primary key,
servicesCount integer default 0 not null check (servicesCount <= 3)
);
create table RoomServices (
roomNumber integer,
service varchar2(100),
comments varchar2(4000)
);
create trigger RoomServiceLimit
before insert or update or delete on RoomServices
for each row
begin
if inserting then
update Room
set servicesCount = servicesCount + 1
where roomNumber = :new.roomNumber;
elsif updating and :old.roomNumber != :new.roomNumber then
update Room
set servicesCount = servicesCount + 1
where roomNumber = :new.roomNumber;
update Room
set servicesCount = servicesCount - 1
where roomNumber = :old.roomNumber;
elsif deleting then
update Room
set servicesCount = servicesCount - 1
where roomNumber = :old.roomNumber;
end if;
end;
/
insert into Room(roomNumber) values (1);
insert into Room(roomNumber) values (2);
insert into RoomServices(roomNumber,service,comments) values (1,'cleaning','first');
insert into RoomServices(roomNumber,service,comments) values (1,'drying','second');
insert into RoomServices(roomNumber,service,comments) values (1,'watering','third');
insert into RoomServices(roomNumber,service,comments) values (1,'something','third'); -- error
select * from room;
insert into RoomServices(roomNumber,service,comments) values (2,'something','2: first');
update RoomServices
set comments = null
where roomNumber = 2;
select * from room;
update RoomServices -- error
set roomNumber = 1
where roomNumber = 2;
select * from room;
delete from RoomServices where roomNumber = 1;
select * from room;
答案 2 :(得分:0)
我假设RoomServices:
注意:您说“超过3项服务”但您的代码说“超过2项服务”。所以我将使用“超过2项服务”。
那么,使用语句触发器呢?
CREATE OR REPLACE TRIGGER RoomServiceLimit
AFTER INSERT OR UPDATE ON RoomServices
DECLARE
badRoomsCount NUMBER;
badRoomsList VARCHAR2(32767); -- adjust the varchar2 size according to your requirements
BEGIN
SELECT COUNT(*), LISTAGG(roomNumber, ', ') WITHIN GROUP (ORDER BY 1)
INTO badRoomsCount, badRoomsList
FROM (SELECT roomNumber FROM RoomServices GROUP BY roomNumber HAVING COUNT(*) > 2);
IF (badRoomsCount > 0) THEN
RAISE_APPLICATION_ERROR(-20001,'Room/s '||badRoomsList||' will have more than 2 services.');
END IF;
END;
/
如果RoomServices很小但有太多更改(插入或更新),那么您可以考虑在RoomNumber上创建一个索引。
如果我的假设是假的,请尝试以下方法:
CREATE GLOBAL TEMPORARY TABLE RoomServicesAux as SELECT roomNumber FROM RoomServices WHERE 1=0;
/
CREATE OR REPLACE TRIGGER PreRoomServiceLimit
BEFORE INSERT OR UPDATE ON RoomServices
BEGIN
DELETE FROM RoomServicesAux;
END;
/
CREATE OR REPLACE TRIGGER RowRoomServiceLimit
BEFORE INSERT OR UPDATE OF roomNumber ON RoomServices FOR EACH ROW
BEGIN
INSERT INTO RoomServicesAux VALUES (:NEW.roomNumber);
END;
/
CREATE OR REPLACE TRIGGER RoomServiceLimit
AFTER INSERT OR UPDATE ON RoomServices
DECLARE
badRoomsCount NUMBER;
badRoomsList VARCHAR2(32767); -- adjust the varchar2 size according to your requirements
BEGIN
SELECT COUNT(*), LISTAGG(roomNumber, ', ') WITHIN GROUP (ORDER BY 1)
INTO badRoomsCount, badRoomsList
FROM (
SELECT roomNumber
FROM RoomServices
WHERE roomNumber in (SELECT roomNumber FROM RoomServicesAux)
GROUP BY roomNumber
HAVING COUNT(*) > 2
);
DELETE FROM RoomServicesAux;
IF (badRoomsCount > 0) THEN
RAISE_APPLICATION_ERROR(-20001,'Room/s '||badRoomsList||' will have more than 2 services.');
END IF;
END;
/
或者如果你有Oracle 11g或更高版本,那么你可以使用复合触发器:
CREATE OR REPLACE TYPE RoomsListType IS TABLE OF INTEGER; -- change to the type of RoomServices.rowNumber
/
CREATE OR REPLACE TRIGGER RoomServiceLimit
FOR INSERT OR UPDATE OF roomNumber ON RoomServices
COMPOUND TRIGGER
RoomsList RoomsListType := RoomsListType();
badRoomsCount NUMBER;
badRoomsList VARCHAR2(32767); -- adjust the varchar2 size according to your requirements
AFTER EACH ROW IS
BEGIN
RoomsList.EXTEND;
RoomsList(RoomsList.COUNT) := :NEW.roomNumber;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
SELECT COUNT(*), LISTAGG(roomNumber, ', ') WITHIN GROUP (ORDER BY 1)
INTO badRoomsCount, badRoomsList
FROM (
SELECT roomNumber
FROM RoomServices
WHERE roomNumber in (SELECT * FROM table(RoomsList))
GROUP BY roomNumber
HAVING COUNT(*) > 2
);
IF (badRoomsCount > 0) THEN
RAISE_APPLICATION_ERROR(-20001,'Room/s '||badRoomsList||' will have more than 2 services.');
END IF;
END AFTER STATEMENT;
END;
/