Триггер Oracle Compound — как хранить и использовать удаленные строки? ИНДЕКС ПО таблице?

Я сейчас веду долгую борьбу за триггер DELETE в Oracle, который при удалении строки выбирает новое значение MAX из оставшихся строк и записывает его в другую таблицу. Наткнувшись на надоедливую ошибку мутирующей таблицы ORA-04091 (не могу прочитать таблицу в FOR EACH ROW), я переключился на составной триггер Oracle.

Как мне лучше всего сохранить удаленные строки (несколько значений в строке, потому что дальнейшая проверка должна обновляться только в том случае, если удаленная оценка могла быть высокой, а не более низкой)? Я боюсь, что глобальная временная таблица может закончиться беспорядком, если несколько событий триггера пересекаются и, например. обновление рекордов запускается для «DeletedMatches», которые на самом деле не были удалены, но зарегистрированы триггерным событием «До».

Могу ли я создать таблицу, которая а) существует только локально в этом триггере б) может использоваться в SQL, как обычные таблицы БД или временные таблицы?

Следующий (псевдо-) код должен обновлять таблицу CurrentHighScores всякий раз, когда совпадение удаляется (старые рекорды исчезают и заменяются наивысшим оставшимся результатом).

CREATE TABLE GameScores (
    MatchId number not null --primary key
    Player  varchar(255) not null,
    Game    varchar(255) not null, -- PacMan, Pong, whatever...
    Score   number not null );

-- High score for each game:
CREATE TABLE CurrentHighScores (
    HiScId number not null --primary key
    Player  varchar(255) not null,
    Game    varchar(255) not null,
    HighScore   number not null );

create or replace TRIGGER UpdHiScoreOnMatchDelete
FOR DELETE ON GameScores 
COMPOUND TRIGGER
    TYPE matchtable IS TABLE OF GameScores%ROWTYPE INDEX BY SIMPLE_INTEGER;
    DeletedMatches matchtable;
    MatchIndex SIMPLE_INTEGER := 0;

BEFORE EACH ROW IS -- collect deleted match scores
BEGIN
  MatchIndex:= MatchIndex+ 1;
  DeletedMatches(MatchIndex).Game := :old.Game;
  DeletedMatches(MatchIndex).Score := :old.Score;
  -- don't want to set every column value, want to 
      -- do like: INSERT :old INTO DeletedMatches;
      -- don't want the Index either!
END BEFORE EACH ROW;

AFTER STATEMENT IS
BEGIN
    UPDATE CurrentHighScores hsc 
    SET hsc.HighScore=(
      select max(gsc.Score) from GameScores gsc
      where hsc.Game=gsc.Game)
    where hsc.Game IN (
      select del.Game from DeletedMatches del where hsc.HighScore = del.Score)
      -- won't work, how can I check within the SQL if a row 
              -- for this game has been deleted, or anyhow integrate 
              -- DeletedMatches into the SQL, without a cursor?
              -- Optional further cond. in subselect, to update only 
              -- if deleted score equals highscore: 
    and exists(
      select 1 from GameScores where Game=hsc.Game); 
      -- ignore games without remaining match scores.

    -- Delete/set zero code for games without existing scores omitted here.
END AFTER STATEMENT;

person Erik Hart    schedule 29.06.2012    source источник
comment
Похоже, что содержимое ГЛОБАЛЬНОЙ ВРЕМЕННОЙ ТАБЛИЦЫ с параметром ON COMMIT DELETE ROWS (по умолчанию) является частным для текущей транзакции, поэтому я считаю, что могу его использовать. Однако это не рекомендуется. Но я пока не знаю, как использовать вложенные таблицы, используемые в примере кода, в SQL-запросах вместе с обычными таблицами. В любом случае триггер ДОЛЖЕН записать максимальное значение оставшихся строк для определенной группы в целевую таблицу (здесь: CurrentHighScores), эта цель на самом деле является таблицей метаданных с данными, в основном не связанными с моделью данных. DELETE обычно затрагивает отдельные строки, редко несколько сотен.   -  person Erik Hart    schedule 30.06.2012


Ответы (2)


«Раздражающая» ошибка изменяющейся таблицы почти всегда указывает на плохой дизайн, как правило, на денормализованную модель данных. Казалось бы, применимо в данном случае. Если вам нужно поддерживать совокупные значения, подсчеты, максимумы и т. д., почему бы не использовать встроенную функциональность Oracle? Oracle предоставил нам объект MATERIALIZED VIEW специально для обработки сводок. Подробнее.

В вашем случае замените CurrentHighScores материализованным представлением.

CREATE MATERIALIZED VIEW CurrentHighScores 
BUILD IMMEDIATE
REFRESH FAST
as select 
( 
    Player , 
    Game    , 
    max(score) as HighScore  
from GameScores 
group by player, game ; 

Вам также потребуется создать МАТЕРИАЛИЗОВАННЫЙ ЖУРНАЛ ПРОСМОТРА на GameScores.

person APC    schedule 29.06.2012
comment
Спасибо, но я не могу изменить реальную таблицу (которая была изменена на CurrentHighScores для публикации здесь) на представление, так как она содержит много разных данных из разных источников. База данных почти такая же, как есть, если бы я добавил представление, мне все равно пришлось бы копировать значение. Составной триггер многообещающий, но мне нужно хорошее временное хранилище для удаленных строк, повторяемых с помощью BEFORE EACH ROW, которое является одновременно локальным для события триггера и может использоваться в SQL. - person Erik Hart; 29.06.2012
comment
Материализованное представление — это, по сути, таблица, а не представление. Это физическое хранилище данных: разница в том, что данные хранятся в главной таблице (таблицах). С другой стороны, представление — это просто сохраненный запрос. - person APC; 01.07.2012

В этом случае глобальная временная таблица выполнила свою работу. Соберите все :old строки в курсоре BEFORE EACH ROW, затем в AFTER STATEMENT соедините временную таблицу с таблицей удаления и найдите новые MAX значения для тех элементов, которые были удалены.

Мои опасения, что записи триггера в глобальном временном файле будут путаться с записями из других событий триггера, как обычно с MSSQL #tempTable, были неверны, ON COMMIT DELETE ROWS работает нормально.

Просто позор, что такая простая триггерная задача, которую нужно выполнить за несколько часов в MSSQL (включая тестирование), заняла ОЧЕНЬ много времени, читая весь фон Oracle. А в Oracle SQL Developer можно легко часами смотреть на какие-то странные загадочные сообщения об ошибках с неправильными номерами строк (по крайней мере, с видимыми строками в качестве ссылки), только чтобы обнаружить, что причиной была отсутствующая точка с запятой в конце строки или неправильно закрытая блокировать.

Материализованные представления могут быть вариантом в будущем, мой вопрос будет таким: являются ли данные matview транзакционными (например, изменения, сделанные триггером), или это функция отложенной отчетности? Я знаю, что его можно настроить на периодическое обновление, но также и с «Быстрым» обновлением, по крайней мере, почти сразу. Это, однако, с более раздражающими ограничениями (такими как отсутствие предложений Where, если необходимо искать значения MAX).

person Erik Hart    schedule 05.07.2012