我有一个跟踪实验室计算机使用情况的系统。稍微简化一下,它可以解决:
实验室有一个关键视图,它收集了与实验室相关的座位总数,以及该实验室当前使用的座位数。
我想要做的是添加历史记录或审核表,以跟踪实验室人口随时间的变化。我在机床工作台上有一个触发器,用于在每次机床工作台更改时将时间和总实验人口存储在我的实验室历史记录表中。问题是,为了获得实验室的新总数,我必须检查机器表中的其他值。这导致表变异错误。
我在这里和其他地方发现的一些事情表明我应该创建一个包来跟踪正在改变的实验室。使用before触发器清除列表,使用行触发器存储要更改的每个labid,使用after触发器更新历史记录表,只为那些id在列表中的实验室更新。我已经尝试过,但无法弄清楚如何访问我存储在包表中的值(或者即使它首先正确存储它们。)毫无疑问,这是显而易见的,我是不熟悉PL / SQL包和表变量 - 引用像数组这样的表条目的整个语法让我感到模糊不清,尽管它非常有用。所以下面的大部分内容都是根据我发现的其他解决方案进行复制和改编的,但是它们并没有延伸到如何实际使用我的变化的lablocids表,假设它首先被正确创建。以下简单告诉我,当我尝试编译最终触发器时,pg_machine_in_use_pkg.changedlablocids不存在。
create or replace package labstats_adm.pg_machine_in_use_pkg
as
type arr is table of number index by binary_integer;
changedlablocids arr;
empty arr;
end;
/
create or replace trigger labstats_adm.pg_machine_in_use_init
before insert or update
on labstats_adm.pg_machine
begin
-- begin each update with a blank list of changed lablocids
pg_machine_in_use_pkg.changedlablocids := pg_machine_in_use_pkg.empty;
end;
/
--
create or replace trigger labstats_adm.pg_machine_in_use_update
after insert or update of in_use,lablocid
on labstats_adm.pg_machine
for each row
begin
-- record lablocids - old and new - of changed machines
if :new.lablocid is not null then
pg_machine_in_use_pkg.changedlablocids( pg_machine_in_use_pkg.changedlablocids.count+1 ) := :new.lablocid;
end if;
if :old.lablocid is not null and :old.lablocid != :new.lablocid then
pg_machine_in_use_pkg.changedlablocids( pg_machine_in_use_pkg.changedlablocids.count+1 ) := :old.lablocid;
end if;
end;
create or replace trigger labstats_adm.pg_machine_lab_history
after insert or update of in_use,lablocid
on labstats_adm.pg_machine
begin
-- for each lablocation we just logged a change to, update that labs history
insert into labstats_adm.pg_lab_history (labid, time, total_seats, used_seats)
select labid, systimestamp, total_seats, used_seats
from labstats_adm.lab_usage
where labid in (
select distinct labid from pg_machine_in_use_pkg.changedlablocids
);
end;
/
另一方面,如果整体方法比包装更好,我全都听见了。
答案 0 :(得分:0)
经过一番反思后,我必须选择@tbone。根据我的经验,历史表应该是“真实”表中数据的副本,其中添加了字段以显示历史表中的行所显示的数据的特定“版本”何时生效。因此,如果“真实”表格类似于
CREATE TABLE REAL_TABLE
(ID_REAL_TABLE NUMBER PRIMARY KEY,
COL2 NUMBER,
COL3 VARCHAR2(50));
然后我将历史表创建为
CREATE TABLE HIST_TABLE
(ID_HIST_TABLE NUMBER PRIMARY KEY
ID_REAL_TABLE NUMBER,
COL2 NUMBER,
COL3 VARCHAR2(50),
EFFECTIVE_START_DT TIMESTAMP(9) NOT NULL,
EFFECTIVE_END_DT TIMESTAMP(9));
并且我有以下触发器来填充所有内容:
CREATE TRIGGER REAL_TABLE_BI
BEFORE INSERT ON REAL_TABLE
REFERENCING OLD AS OLD
NEW AS NEW
FOR EACH ROW
BEGIN
IF :NEW.ID_REAL_TABLE IS NULL THEN
:NEW.ID_REAL_TABLE := REAL_TABLE_SEQUENCE.NEXTVAL;
END IF;
END REAL_TABLE_BI;
CREATE TRIGGER HIST_TABLE_BI
BEFORE INSERT ON HIST_TABLE
FOR EACH ROW
BEGIN
IF :NEW.ID_HIST_TABLE IS NULL THEN
:NEW.ID_HIST_TABLE := HIST_TABLE_SEQUENCE.NEXTVAL;
END IF;
END HIST_TABLE_BI;
CREATE TRIGGER REAL_TABLE_AIUD
AFTER INSERT OR UPDATE OR DELETE ON REAL_TABLE
FOR EACH ROW
DECLARE
tsEffective_start_date TIMESTAMP(9) := SYSTIMESTAMP;
tsEffective_end_date TIMESTAMP(9) := dtEffective_start_date - INTERVAL '0.000000001' SECOND;
BEGIN
IF UPDATING OR DELETING THEN
UPDATE HIST_TABLE
SET EFFECTIVE_END_DATE := tsEffective_end_date
WHERE ID_REAL_TABLE = :NEW.ID_REAL_TABLE AND
EFFECTIVE_END_DATE IS NULL;
END IF;
IF INSERTING OR UPDATING THEN
INSERT INTO HIST_TABLE (ID_REAL_TABLE, COL2, COL3, EFFECTIVE_START_DATE)
VALUES (:NEW.ID_REAL_TABLE, :NEW.COL2, :NEW.COL3, tsEffective_start_date);
END IF;
END REAL_TABLE_AIUD;
使用此方法,“历史”表格具有“真实”表格中所有数据的历史版本 PLUS 来自“真实”表格的“当前”数据的完整副本;这样做是为了简化需要报告表中所有数据版本的查询,包括当前值。
使用触发器执行所有操作的优点是主键和历史记录表的维护变为自动且不容易被绕过或遗忘。
分享并享受。
答案 1 :(得分:0)
感谢Bob Jarvis指出我的复合触发器,它显着地清理了整体结构。在那之后,我只需要清理我从表变量中获取值的方式。如果有人偶然发现这个寻找同一问题答案的奇怪机会,我会在这里发布我的最终解决方案:
create or replace
trigger pg_machine_in_use_update
for insert or update or delete of in_use,lablocid
on labstats_adm.pg_machine
compound trigger
type arr is table of number index by binary_integer;
changedlabids arr;
idx binary_integer;
after each row is
newlabid labstats_adm.pg_labs.labid%TYPE;
oldlabid labstats_adm.pg_labs.labid%TYPE;
begin
-- store the labids of any changed locations
-- PL/SQL does not like us testing for the existence of something that isn't there, so just set it twice if necessary
if ( :new.lablocid is not null ) then
select labid into newlabid from labstats_adm.pg_lablocation where lablocid = :new.lablocid;
changedlabids( newlabid ) := 1;
end if;
if ( :old.lablocid is not null ) then
select labid into oldlabid from labstats_adm.pg_lablocation where lablocid = :old.lablocid;
changedlabids( oldlabid ) := 1;
end if;
end after each row;
after statement is
begin
idx := changedlabids.FIRST;
while idx is not null loop
insert into labstats_adm.pg_lab_history (labid, time, total_seats, used_seats)
select labid, systimestamp, total_seats, used_seats
from labstats_adm.lab_usage
where labid = idx;
idx := changedlabids.NEXT(idx);
end loop;
end after statement;
end pg_machine_in_use_update;