SQL表正在变异…错误

问题描述 投票:2回答: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;    
/

我已经尝试通过插入和更新分别运行每个方法,并且插入始终有效,而更新始终会给我带来变异表错误。我不知道该如何解决这个问题,因此,任何建议都将不胜感激。

谢谢!

oracle plsql database-trigger
3个回答
2
投票

没有reliable使用触发器来实施这种约束的方法。一种可能的方法是使用实​​例化视图,该视图在提交时自动刷新并具有强制执行您的业务规则的检查约束:

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异常(实例化视图刷新路径中的错误)。


0
投票

我假设是RoomServices:

  • a)是一个未经大量修改的小表
  • b)永远不会有提供3个以上服务的房间

注意:您说的是“ 3个以上的服务”,但是您的代码说的是“ 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;
/

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;
© www.soinside.com 2019 - 2024. All rights reserved.