1

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

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

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

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

我在这里和其他地方发现的一些东西建议我应该创建一个包来跟踪正在更改的实验室。使用前触发器来清除列表,使用行触发器来存储每个被更改的 labid,并使用后触发器来更新历史记录表,只为那些 id 在列表中的实验室使用新值。我已经尝试过了,但无法弄清楚如何访问我存储在包表中的值(或者即使它首先正确地存储了它们。)毫无疑问,我是不熟悉 PL/SQL 包和表变量 - 引用数组之类的表条目的整个语法让我觉得有点异端,但如果它有效的话却非常有用。所以下面的大部分内容只是从我找到的其他解决方案中复制和改编的,但他们没有 t 延伸至如何实际使用我的已更改 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;
/

另一方面,如果有比包装更好的整体方法,我会全力以赴。

4

2 回答 2

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;

使用这种方法,“历史”表具有“真实”表中数据的所有历史版本加上“真实”表中“当前”数据的完整副本这样做是为了简化查询,这些查询需要报告表中所有版本的数据,包括当前值。

使用触发器来做这一切的好处是主键和历史表的维护变得自动化,不容易被规避或遗忘。

分享和享受。

于 2013-08-06T11:53:53.457 回答
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;
于 2013-08-09T22:20:07.783 回答