PL/SQL before insert trigger禁止插入两条年龄大于10年的记录
PL/SQL before insert trigger to prohibit insertion of two records that have age greater than 10 years
BEFORE INSERT TRIGGER 必须执行以下操作:
- 禁止将年龄相差超过 10 岁的两名患者分配到同一房间。
- 此外,触发器还应禁止将允许年龄(小于或等于 10 岁)的两名患者分配到同一张床上。
这意味着:
- 如果新患者比任何已入院患者大 10 岁左右,则他们不能在同一个房间。
- 如果新患者与任何已入院患者的年龄相差不到 10 年,他们可以在同一个房间但不在同一张床上。
患者table结构:
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');
触发器假设将新行与 table 中已存在的行进行比较。但到目前为止,我的触发器一直只在新行之间进行比较,而不是与 table.
中已经存在的行进行比较
如何让它按预期工作?我一直在到处搜索,但无论如何都找不到与整个 table/database.
进行比较
非常感谢。
关系数据库可以轻松定义一对一关系、一对多关系和多对多关系。选择是零、一或任何多于一的数字。定义 1-2 关系并不容易。
这是一个想法。首先一个table来定义每个房间。每个房间有一个入口。
create table Rooms(
ID int identity,
... -- other room-related data
constraint PK_Rooms primary key( ID )
);
接下来 table 定义每个房间的床位。每个房间的每张床都有一个条目。
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 )
);
表 Rooms
和 Room_Beds
一旦定义,就是 stable。它们的内容在日常操作中保持不变。接下来table保留完整的床铺使用历史。当条目被占用时插入一个条目,当条目未被占用时插入另一个条目。请注意,如果房间仅定义了两张床,则无法输入 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 )
);
从技术上讲,没有什么可以阻止同一张床在同一时间段内"occupied"。这是通过仅从该视图中选择占用床位以编程方式防止的:
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 为空则未 列出.
必须处理时序,但这几乎是大多数应用程序的既定条件。这是因为一旦在 Bed_Patient
中插入一行且 PatientID 字段中的值为非空值,该房间就不再出现在 Available_Beds
视图中。
好消息:你的触发器有效(不完全但几乎)。
我不知道你的意思:但到目前为止,我的触发器一直只在新行之间进行比较,而不是与 table.[=30 中已经存在的行进行比较=]
您的触发器显然会检查 table 中已经存在的行。它工作正常。当然如果你创建table,插入一些冲突的行
接下来你创建触发器它不会纠正以前的插入 - 它们必须以其他方式消除(删除或更新)。
坏消息: 如果您尝试在一个行中插入多行 select,您的触发器将不起作用,就像这里一样,您将得到错误 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。
BEFORE INSERT TRIGGER 必须执行以下操作:
- 禁止将年龄相差超过 10 岁的两名患者分配到同一房间。
- 此外,触发器还应禁止将允许年龄(小于或等于 10 岁)的两名患者分配到同一张床上。
这意味着:
- 如果新患者比任何已入院患者大 10 岁左右,则他们不能在同一个房间。
- 如果新患者与任何已入院患者的年龄相差不到 10 年,他们可以在同一个房间但不在同一张床上。
患者table结构:
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');
触发器假设将新行与 table 中已存在的行进行比较。但到目前为止,我的触发器一直只在新行之间进行比较,而不是与 table.
中已经存在的行进行比较如何让它按预期工作?我一直在到处搜索,但无论如何都找不到与整个 table/database.
进行比较非常感谢。
关系数据库可以轻松定义一对一关系、一对多关系和多对多关系。选择是零、一或任何多于一的数字。定义 1-2 关系并不容易。
这是一个想法。首先一个table来定义每个房间。每个房间有一个入口。
create table Rooms(
ID int identity,
... -- other room-related data
constraint PK_Rooms primary key( ID )
);
接下来 table 定义每个房间的床位。每个房间的每张床都有一个条目。
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 )
);
表 Rooms
和 Room_Beds
一旦定义,就是 stable。它们的内容在日常操作中保持不变。接下来table保留完整的床铺使用历史。当条目被占用时插入一个条目,当条目未被占用时插入另一个条目。请注意,如果房间仅定义了两张床,则无法输入 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 )
);
从技术上讲,没有什么可以阻止同一张床在同一时间段内"occupied"。这是通过仅从该视图中选择占用床位以编程方式防止的:
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 为空则未 列出.
必须处理时序,但这几乎是大多数应用程序的既定条件。这是因为一旦在 Bed_Patient
中插入一行且 PatientID 字段中的值为非空值,该房间就不再出现在 Available_Beds
视图中。
好消息:你的触发器有效(不完全但几乎)。
我不知道你的意思:但到目前为止,我的触发器一直只在新行之间进行比较,而不是与 table.[=30 中已经存在的行进行比较=] 您的触发器显然会检查 table 中已经存在的行。它工作正常。当然如果你创建table,插入一些冲突的行 接下来你创建触发器它不会纠正以前的插入 - 它们必须以其他方式消除(删除或更新)。
坏消息: 如果您尝试在一个行中插入多行 select,您的触发器将不起作用,就像这里一样,您将得到错误 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。