2016-10-05 3 views
1

Это мой триггер insert на Table_A, где я храню параметры в своей системе. Когда я вставляю в таблицу, я хочу изменить end_date последней записи, чтобы сохранить запись в версиях.Обновление предыдущей версии записи во время вызова Oracle insert

create or replace trigger parameter_version 
    before insert 
    on parameters 
    for each row 
declare 
    v_is_exist number := 0; 
    v_rowid rowid; 
begin 
    select count(*) into v_is_exist from parameters where name = :new.name; -- check if parameter exist 
    select rowid into v_rowid from parameters where name = :new.name and end_date is null; -- record rowid, which sholud be changed  
    if v_is_exist <> 0 then  
    set end_date = :new.start_date - 1 
    end if; 
end; 

Ситуации в таблице перед вставкой является:

| id | name | value | start_date | end_date | 
----------------------------------------------- 
| 1 |Par_A | 10 | 2016-09-01 | 2016-10-01 | 
----------------------------------------------- 
| 2 |Par_A | 20 | 2016-10-02 | 2016-10-03 | 
----------------------------------------------- 
| 3 |Par_A | 30 | 2016-10-05 | <null> | 
----------------------------------------------- 

Записи с идентификатором = 3 следует установить END_DATE на: new_start_date - 1 (близкий вариант) и вставив запись у меня есть следующая версия пар с датой_начал = sysdate.

У меня есть имя ошибки ORA-04091, это мутирует, триггер/функция может его не видеть.

Я знаю, что этот случай тяжелый и, вероятно, невозможно, но, возможно, кто-то знает решение? Или, может быть, существует другое решение в этом случае?

+1

Это нехороший кандидат на использование триггера. Как следует из сообщения об ошибке, содержимое таблицы все еще находится в потоке, и триггер может не запускаться после обновления всех соответствующих строк. Намного лучше, чтобы код вызова делал необходимые обновления. Если это твердое требование, вам лучше всего будет исследовать [сложные триггеры] (https://docs.oracle.com/database/121/LNPLS/triggers.htm#LNPLS2005) –

+1

В зависимости от версии вашей базы данных Oracle есть посмотрите на [составные триггеры] (http://viralpatel.net/blogs/compound-triggers-in-oracle-11g-tutorial-example/) (только в крайнем случае, я думаю, вы должны пересмотреть свой дизайн) – sers

+0

You необходимо использовать триггер уровня инструкции. Триггеры уровня строки не могут изменять обновляемую таблицу. –

ответ

0

Попробуйте что-то вроде этого:

create or replace trigger parameter_version 
    before insert 
    on parameters 
    for each row 
begin 
    /*Don't care if there's 0 rows updated */ 
    update parameters 
     set end_date = :new.start_date - 1 
    where name = :new.name and end_date is null; 

    :new.end_date := null; 
end; 
+1

Вы не можете сделать это в Oracle - это вызовет позорную ошибку «table is mutating» –

+0

ok, так как у меня была эта ошибка, не была уверена. Я бы просто сделал это в процедуре/пакете, который обрабатывает вставку. –

0

Вы можете справиться с этим с после заявления триггера с LEAD аналитической функции:

DROP TABLE demo; 

CREATE TABLE demo(id   NUMBER 
       , name  VARCHAR2(30) 
       , VALUE  NUMBER 
       , start_date DATE 
       , end_date DATE 
       ); 

INSERT INTO demo(id, name, VALUE, start_date, end_date) 
    VALUES (1, 'Par_A', 10, TO_DATE('2016-09-01', 'YYYY-MM-DD'), TO_DATE('2016-10-01', 'YYYY-MM-DD')); 

INSERT INTO demo(id, name, VALUE, start_date, end_date) 
    VALUES (2, 'Par_A', 20, TO_DATE('2016-10-02', 'YYYY-MM-DD'), TO_DATE('2016-10-04', 'YYYY-MM-DD')); 

INSERT INTO demo(id, name, VALUE, start_date) 
    VALUES (3, 'Par_A', 30, TO_DATE('2016-10-05', 'YYYY-MM-DD')); 

INSERT INTO demo(id, name, VALUE, start_date) 
    VALUES (4, 'Par_A', 40, TO_DATE('2016-10-07', 'YYYY-MM-DD')); 

INSERT INTO demo(id, name, VALUE, start_date) 
    VALUES (5, 'Par_A', 50, TO_DATE('2016-10-11', 'YYYY-MM-DD')); 

COMMIT; 

SELECT id 
     , name 
     , start_date 
     , end_date 
     , LEAD(start_date) OVER(PARTITION BY name ORDER BY start_date) - 1 AS new_date 
    FROM demo 
    WHERE end_date IS NULL 
ORDER BY id; 

CREATE OR REPLACE TRIGGER demo_aius 
    AFTER INSERT OR UPDATE 
    ON demo 
    REFERENCING NEW AS new OLD AS old 
DECLARE 
    CURSOR c_todo 
    IS 
     SELECT id, new_date 
      FROM (SELECT id 
        , name 
        , start_date 
        , end_date 
        , LEAD(start_date) OVER(PARTITION BY name ORDER BY start_date) - 1 AS new_date 
        FROM demo 
       WHERE end_date IS NULL) 
     WHERE new_date IS NOT NULL; 
BEGIN 
    FOR rec IN c_todo 
    LOOP 
     UPDATE demo 
      SET end_date = rec.new_date 
     WHERE id = rec.id; 
    END LOOP; 
END demo_aius; 
/

INSERT INTO demo(id, name, VALUE, start_date) 
    VALUES (6, 'Par_A', 60, TO_DATE('2016-10-15', 'YYYY-MM-DD')); 

COMMIT; 

SELECT id 
     , name 
     , start_date 
     , end_date 
    FROM demo 
ORDER BY id; 

Как показывает сценарий, такое обновление может даже работать с несколькими отсутствующие даты окончания, если триггер был случайно отключен. Параметр «PARTITION BY name» гарантирует, что он также функционирует после сложных операторов вставки.

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

+0

Я пробовал это решение, и у меня все еще есть ошибка ora-04091. PL/SQL Developer отмечает строку, в которой определяется курсор c_todo (выберите id ...) – seeksoul

+0

Привет, смотри, этот триггер работает только _without_ ДЛЯ КАЖДОЙ РЯДЫ. Если это не проблема, отправьте _all_ триггеры в таблице, которую вы тестируете с помощью –

Смежные вопросы