历史表引用表/访问包表变量中的其他值

时间:2013-08-05 23:17:22

标签: sql oracle plsql

我有一个跟踪实验室计算机使用情况的系统。稍微简化一下,它可以解决:

  • 机器与实验室相关联。
  • 计算机具有二进制的logged_in状态,当用户登录和注销时会自动更新。

实验室有一个关键视图,它收集了与实验室相关的座位总数,以及该实验室当前使用的座位数。

我想要做的是添加历史记录或审核表,以跟踪实验室人口随时间的变化。我在机床工作台上有一个触发器,用于在每次机床工作台更改时将时间和总实验人口存储在我的实验室历史记录表中。问题是,为了获得实验室的新总数,我必须检查机器表中的其他值。这导致表变异错误。

我在这里和其他地方发现的一些事情表明我应该创建一个包来跟踪正在改变的实验室。使用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;
/

另一方面,如果整体方法比包装更好,我全都听见了。

2 个答案:

答案 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;