Arhn - архитектура программирования

Таблица истории, ссылающаяся на другие значения в таблице/доступ к переменным таблицы пакета

У меня есть система для отслеживания использования компьютеров в лаборатории. Немного упрощая, получается:

  • Машины связаны с лабораторией.
  • Машины имеют двоичное состояние logged_in, которое автоматически обновляется при входе и выходе пользователей.

В лаборатории есть представление, которое собирает общее количество рабочих мест, связанных с лабораторией, и текущее количество, используемое для этой лаборатории.

Что я хотел бы сделать, так это добавить историю или таблицу аудита, которая будет отслеживать изменения в численности лаборатории с течением времени. У меня был триггер в таблице машин, чтобы сохранять время и общее количество лабораторий в моей таблице истории лабораторий каждый раз, когда таблица машин менялась. Проблема в том, что для того, чтобы получить новую сумму для лаборатории, я должен проверить другие значения в таблице машины. Это приводит к ошибке изменения таблицы.

Некоторые вещи, которые я нашел здесь и в других местах, предложили мне создать пакет для отслеживания изменяемых лабораторий. Используйте триггер «до» для очистки списка, триггер «строка» для сохранения каждой измененной лабиды и триггер «после» для обновления таблицы истории новыми значениями только для тех лабораторий, идентификаторы которых есть в списке. Я пробовал это, но не могу понять, как получить доступ к значениям, которые я сохранил в таблице пакетов (или даже правильно ли они хранятся в первую очередь). Как, без сомнения, будет очевидно, я не знаком с пакетами PL/SQL и табличными переменными - весь синтаксис обращения к записям таблицы, таким как массивы, показался мне смутно еретическим, хотя и невероятно полезным, если он работает. Таким образом, большая часть приведенного ниже просто скопирована и адаптирована из других решений, которые я нашел, но они не касались того, как на самом деле использовать мою таблицу измененных лаблоцидов, предполагая, что она изначально создана правильно. Следующее просто говорит мне, что 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;
/

С другой стороны, если есть лучший общий подход, чем пакет, я весь внимание.

05.08.2013

  • Возможно, вы захотите исследовать использование составных триггеров, которые могут помочь обойти проблемы с изменением таблиц. См. примеры триггеров Oracle. Делитесь и наслаждайтесь. 06.08.2013
  • Кажется слишком сложным. Почему бы просто не создать таблицу истории так же, как и основную таблицу, и использовать триггер для вставки в историю при обновлении или удалении? См. этот пост для примера. 06.08.2013

Ответы:


1

После некоторого размышления я должен пойти с @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;

Используя этот метод, в таблице «история» есть все исторические версии данных в «настоящей» таблице ПЛЮС полная копия «текущих» данных из «настоящей» таблицы; это делается для упрощения запросов, которые должны сообщать обо всех версиях данных в таблице вплоть до текущих значений включительно.

Преимущество использования триггеров для всего этого заключается в том, что обслуживание первичных ключей и таблицы истории становится автоматическим, и его нельзя легко обойти или забыть.

Делитесь и наслаждайтесь.

06.08.2013
  • Проблема с этим методом в том, что он становится крайне неэффективным, если не невозможным, для вычисления нужного мне значения — населения лаборатории — в исторический момент времени. Используя эту модель, у меня будет история изменений в поле logged_in отдельных машин. Таким образом, чтобы найти население лаборатории в определенный момент времени, мне пришлось бы просмотреть каждую запись за всю историю лаборатории до этого момента, чтобы рассчитать население в этот момент, потому что кто-то мог войти в систему через 3 года. назад и никогда не выходил из системы. 07.08.2013
  • (Правда? У нас есть /такое/ короткое ограничение на количество символов в комментариях?) И тогда машина могла перейти из одной лаборатории в другую, поэтому я должен /также/ вести историю изменений членства машин в лаборатории , и запустите целый отдельный подзапрос, чтобы сначала получить список машин, связанных с этой лабораторией в этот момент времени. Между тем, меня никогда не волнует историческое состояние отдельной машины, а только население лаборатории. И гораздо проще рассчитать население лаборатории в момент его изменения и сохранить только ту информацию, которая мне понадобится позже. 07.08.2013

  • 2

    Извините, что так медленно возвращаюсь; это заняло у меня немного возни, и у меня не было много времени, чтобы поработать над этим.

    Спасибо Бобу Джарвису за то, что он указал мне на составные триггеры, которые значительно очистили общую структуру. После этого мне просто нужно было очистить способ, которым я возвращаю значения из своей табличной переменной. На случай, если кто-то еще наткнется на это в поисках ответа на ту же проблему, я опубликую свое окончательное решение здесь:

    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;
    
    09.08.2013
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..