Oracle SQL表正在发生变化。。。错误

Oracle SQL表正在发生变化。。。错误,oracle,plsql,database-trigger,Oracle,Plsql,Database Trigger,我正在尝试写一个触发器,它将禁止医院的任何房间提供3项以上的服务。table RoomServices有一个房间号和一项服务。因此,确定这一点的唯一方法是根据房间号对房间进行分组,并计算服务数量。我尝试过以下代码: CREATE TRIGGER RoomServiceLimit BEFORE INSERT OR UPDATE ON RoomServices FOR EACH ROW DECLARE numService NUMBER; CURSOR C1 IS SELECT co

我正在尝试写一个触发器,它将禁止医院的任何房间提供3项以上的服务。table 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分别运行每个方法,insert总是有效的,而update总是给我一个变化表错误。我不知道如何解决这个问题,所以任何建议都将不胜感激


谢谢

如果没有一些解决办法,您似乎无法解决这个问题。如果没有更好的方法,请查看以下内容:

我猜你有一个桌子室,否则就创建一个:

alter table Room add (
  servicesCount integer default 0 not null check (servicesCount <= 3)
);
然后扣动扳机

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;
drop table Room;
投递桌室服务;
创建桌子室(
roomNumber整数主键,

ServiceScont整数默认0非空检查(ServiceScont我假设RoomServices:

  • a) 是一个小表,没有进行大量修改
  • b) 永远不会有一个房间提供超过3项服务
注意:您说的是“超过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;
/

如果RoMServices很小,但有太多的更改(插入或更新),那么您可以考虑创建一个ROM号的索引。 如果我的假设是错误的,请尝试以下方法:

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;
/
没有可靠的方法使用触发器强制执行这种约束。一种可能的方法是使用物化视图,该视图在提交时自动刷新,并具有强制执行业务规则的检查约束:

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);  
创建表室服务(
主键号不为空,
房间号码);
创建物化视图mv_roomservices
提交时刷新为
挑选
主键,
房间号,
按房间号(分区)计算(*)为cnt
来自客房服务部;
alter table mv_roomservices添加约束

每间客房的chk_max_2_服务检查(cnt相信我XXX在一个URL中,一些成人过滤ISP过滤此URL的结果。这是Oracle,不是MySql触发器,错误消息也来自Oracle,请编辑问题并将
MySql
标记替换为
Oracle
。你为什么要将光标用于更新部分而不是插入部分?也是唯一的方法要在更新中使每个房间的服务计数从2变为3,就是要更新一个房间号……这里没有解决方案,只需注释。尝试使用语句级触发器而不是行级触发器。删除每行的
,然后再次尝试运行DML。有什么区别吗?您仍在尝试访问s中可能已更新的行ame表-这将再次触发突变问题是试图使用行触发器访问表。statemet触发器可以访问其表。RDBMS尝试确保4个基本原则,即ACID原则(原子性、一致性、隔离性和持久性).在语句级原子性意味着您将insert/delete/update视为单个更改。如果您执行的单个语句更改了许多行,原子性确保您可以在第一次更改之前和最后一次更改之后看到表,但任何人都不必在第一次更改和最后一次更改之间看到表。表的变异问题是tipically(但不是唯一)与访问其基表的行触发器和多行insert/update/delete语句关联。抱歉,这是完全错误的。您在每次更新时增加计数器,甚至不检查更新中更改了哪些字段。因此,当我在roomservices中有一个注释列并更改该注释两次时,这触发器将引发错误。这也无法处理任何删除(其中必须减少ServiceScont).是的,但我没有假装完全解决问题。你在这里讲的所有事情都是无用的,不包含任何无法解决的问题。一个主题的主要问题是解决变异表问题。我展示了这个想法。这很公平。但即使你发布了完整的触发器,它也无法可靠地工作,因为你无法解决变异表问题触发器出现问题。这在Oracle中根本不可能。请参阅多用户并发上的线程和相关线程。真的吗?我无法解决它,而且它不工作?请参阅更新版本并运行脚本。不可能?没有什么是不可能的!当然,多用户并发是一个问题,但这是整个问题的一个问题,beca无论如何,use author尝试使用触发器解决它。这不是唯一的方法。此外,提交时的mat.view非常慢,我不愿意为每个检查目的使用表的完整副本。第二行失败:insert in roomservices values(12,3);insert in roomservices values(12,4);insert in roomservices values(12,5);如果您尝试在同一事务中插入两行以上的数据-是的。您必须回滚并重试。这种方式非常有趣。但这是否意味着我也不能执行大规模操作?例如更新所有行,或从查询数据集中插入?
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);