【问题标题】:PL/SQL before insert trigger to prohibit insertion of two records that have age greater than 10 yearsPL/SQL 前插入触发器禁止插入两条年龄大于 10 年的记录
【发布时间】:2015-07-12 13:50:02
【问题描述】:

BEFORE INSERT TRIGGER 必须执行以下操作:

  • 禁止将两名患者分配到年龄相差超过 10 岁的同一房间。
  • 此外,触发器还应禁止将允许年龄(小于或等于 10)的两名患者分配到同一张床位。

这意味着:

  • 如果新患者比任何已经入院的患者大 10 年左右,他们就不能在同一个房间里。
  • 如果新患者与已经入院的患者相距不到 10 年,他们可以在同一个房间,但不能在同一张床上。

患者表结构:

   PAT_ID         CHAR
   PAT_NAME       CHAR
   PAT_GENDER     CHAR
   PAT_AGE        NUMBER
   PAT_ADMIT_D    CHAR
   PAT_WING       CHAR
   PAT_ROOM#      NUMBER
   PAT_BED        CHAR

到目前为止,我的代码是:

   CREATE OR REPLACE TRIGGER assignmentcheck 
   before insert on patient
   for each row

   declare
        cursor p1_cursor is
             select pat_age, pat_room#, pat_bed from patient;

   agediff number;
   begin

        for p1_pat in p1_cursor loop
        agediff :=  p1_pat.pat_age - :new.pat_age;
        if (agediff >10) then
             if (:new.pat_room# = p1_pat.pat_room#) then
             raise_application_error(-20001, 'Age difference greater than 10     cannot be in the same room');
        end if;
        else if (:new.pat_room# = p1_pat.pat_room#) and (:new.pat_bed =  p1_pat.pat_bed) then
        raise_application_error(-20002, 'Age difference less than 10 cannot be on the same bed');
        end if;
        end if;
  end loop;

 end;
 /

一些测试sql:

 insert into patient values ('AZ24523', 'Zhou, Alicia', 'F', 24, '14-APR-2015', 'A', 20, 'B');
 insert into patient values ('JA33234', 'Abbott, John', 'M', 50, '14-APR-2015', 'A', 16, 'B');
 insert into patient values ('AN32676', 'Newman, Andrew', 'M', 10, '14-APR-2015', 'A', 16, 'B');
 insert into patient values ('ZZ24523', 'Zhang, Zhaoping', 'F', 38, '14-APR-2015', 'A', 16, 'A');

触发器假定将新行与表中已存在的行进行比较。但到目前为止,我的触发器只在新行之间进行比较,而不是与表中已经存在的行进行比较。

如何让它按预期工作?我一直在到处搜索,但无论如何都找不到与整个表/数据库进行比较。

非常感谢。

【问题讨论】:

  • 不相关,但是:我几乎可以肯定你确实想要char 数据类型,因为它的长度大小和填充是固定的。请改用varchar
  • 我知道医院人满为患是个大问题,但每张病床不止一个病人?真的吗?
  • 这就是问题的产生方式,绝对不是现实生活中发生的事情。

标签: oracle plsql database-trigger


【解决方案1】:

关系数据库可以轻松定义 1-1 关系、1-many 关系和 many-many 关系。选择是零、一或任意多于一。定义 1-2 关系并不容易。

这是一个想法。首先是一张桌子来定义每个房间。每个房间都有一个入口。

create table Rooms(
    ID    int  identity,
    ... -- other room-related data
    constraint PK_Rooms primary key( ID )
);

接下来是一张表格,用于定义每个房间的床位。每个房间的每张床都有一个入口。

create table Room_Beds(
    RoomID int not null,
    BedID  smallint not null,
    constraint PK_Room_Beds primary key( RoomID, BedID ),
    constraint FK_Room_Beds_Room foreign key( RoomID )
        references Beds( ID )
);

RoomsRoom_Beds,一旦定义,就很稳定。它们的内容在日常操作中保持不变。下表保留了完整的床使用历史。一个条目在被占用时插入,另一个在未被占用时插入。请注意,如果房间只定义了两张床,则无法输入 BedID 3(或更高)的条目。

create table Bed_Patient(
    RoomID     int not null,
    BedID      smallint not null,
    StartDate  date not null,
    PatientID  int,
    IsOccupied as case when PatientID is null then 'N' else 'Y' end,
    constraint PK_Bed_Patient primary key( RoomID, BedID, StartDate )
);

从技术上讲,没有什么可以阻止同一张床在同一时间段内被“占用”。通过仅从此视图中选择占用的床位,可以通过编程方式防止这种情况发生:

create view Available_Beds as
select  rb.*
from    Room_Beds rb
join    Bed_Patient bp
    on  bp.RoomID = rb.ID
    and bp.IsOccupied = 'N'
    and bp.StartDate =(
        select  Max( Start_Date )
        from    Bed_Patient
        where   RoomID = bp.RoomID
            and BedID  = bp.BedID );

现在Bed_Patient 上的插入触发器可以检查如果 PatientID 不为空,则床在视图中列出,或者如果 PatientID 为空,则列出。

必须处理时间,但这对于大多数应用程序来说几乎是给定的。这得益于以下事实:一旦在 PatientID 字段中插入具有非空值的行在 Bed_Patient 中,该房间就不再出现在 Available_Beds 视图中。

【讨论】:

    【解决方案2】:

    好消息:您的触发器有效(不是完全有效,而是几乎有效)。

    我不明白你的意思:但到目前为止,我的触发器只比较新行,而不是与表中已经存在的行进行比较。 您的触发器显然会检查表中已经存在的行。它工作正常。当然如果你创建表,插入一些冲突的行 接下来创建触发器,它不会更正以前的插入 - 它们必须以其他方式消除(删除或更新)。

    坏消息:如果您尝试在一次选择中插入多行,您的触发器将不起作用,就像这里一样,您将收到错误SQL Error: ORA-04091: table CHRIS.PATIENT is mutating, trigger/function may not see it

    insert into patient  
      select 'AZ24523', 'Zhou',   'F', 24, '2015-04-14', 'A', 20, 'B' from dual union all
      select 'JA33234', 'Abbott', 'M', 50, '2015-04-14', 'A', 16, 'B' from dual union all
      select 'AN32676', 'Newman', 'M', 10, '2015-04-14', 'A', 16, 'B' from dual union all
      select 'ZZ24523', 'Zhang',  'F', 38, '2015-04-14', 'A', 16, 'A' from dual;
    

    在这种情况下可以做什么?在 Oracle 11g 中,您可以将代码更改为 compound trigger

    create or replace trigger assignmentcheck 
    for insert on patient compound trigger
    
      type patients_t is table of patient%rowtype;
      v_patients patients_t := patients_t();
      v_patient patient%rowtype;
    
    before statement is begin
      select * bulk collect into v_patients from patient; 
    end before statement;
    
    before each row is begin
      v_patients.extend;
      v_patients(v_patients.last).pat_id := :new.pat_id;
      v_patients(v_patients.last).pat_age := :new.pat_age;
      v_patients(v_patients.last).pat_room# := :new.pat_room#;
      v_patients(v_patients.last).pat_bed := :new.pat_bed;
    end before each row;
    
    after each row is begin
      for i in 1..v_patients.count() loop
        v_patient := v_patients(i);
        if v_patient.pat_id<>:new.pat_id then 
          if v_patient.pat_room# = :new.pat_room# then 
            if abs(v_patient.pat_age-:new.pat_age) > 10 then 
              raise_application_error(-20001, 'Age difference to big');
            elsif v_patient.pat_bed = :new.pat_bed then
              raise_application_error(-20002, 'Bed already taken');
            end if;
          end if;
        end if;
      end loop;
    end after each row;
    
    end assignmentcheck;
    

    在以前的 Oracle 版本中,您可以使用本文中讨论的解决方案之一:Mutating Table Exceptions

    【讨论】:

    • 啊,抱歉,这很简单 - 只需更改年龄条件,使用函数 abs 就像在我的触发器中...if abs(agediff)&gt;10... 它应该会有所帮助。
    • 太棒了!函数 abs() 实际上确实解决了我的问题。当我减去年龄时,我认为问题来自负年龄。抱歉,由于缺乏声誉,我无法为您的答案投票,但感谢您帮助我。
    • 但你可以接受答案 ;-) 你不害怕这个“变异表”错误吗?另外我认为也许你应该为翼添加条件,因为翼A可能有101房间,B翼可能有101房间。
    • 如何点击接受答案?对不起,我是stackoverflow的新手。关于变异错误,我已经测试了大约 20 个插入,但我没有收到错误消息。我不知道为什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    • 2013-10-12
    • 1970-01-01
    • 1970-01-01
    • 2012-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多