powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Подсчет текущего остатка триггером
18 сообщений из 18, страница 1 из 1
Подсчет текущего остатка триггером
    #39276797
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Написал триггер для подсчета текущего остатка по счету, он не всегда срабатывает. Т.е. иногда появляется неверная сумма в поле acc.current_rest. Не могу понять что я сделал неверно.
DDL (не абсолютно точный но в сути полностью повторяющий боевой код) под спойлером.

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
-- счета клиентов
create table acc(id integer not null primary key, client integer not null, account_type integer not null, current_rest number default 0 not null);

-- движение по счетам клиентов
create table acc_move(id integer not null primary key, acc integer not null references acc(id), dt date not null, value number not null);

-- тип для хранения одного движения
create type t_acc_move as object(acc integer, value number);
/

-- коллекция для накопления изменений
create type tt_acc_move as table of t_acc_move;
/

-- триггер для подсчета остатка
CREATE OR REPLACE TRIGGER trg$acc_move_rest
  for insert or update or delete on acc_move
  compound trigger

  l_moves tt_acc_move;

  before statement is
  begin
    l_moves := tt_acc_move();
  end before statement;

  before each row is
    l_idx    number := l_moves.count + 1;
    l_values t_acc_move := t_acc_move(0, 0);
  begin
    l_moves.extend(1);
    if inserting
    then
      l_values.acc := :new.acc;
      l_values.value := :new.value;
    elsif updating
    then
      if :new.acc = :old.acc
      then
        l_values.acc := :new.acc;
        l_values.value := :new.value - :old.value;
      else
        l_moves.extend(1);

        l_values.acc := :old.acc;
        l_values.value := - :old.value;
        l_moves(l_idx + 1) := l_values;

        l_values.acc := :new.acc;
        l_values.value := :new.value;
      end if;
    elsif deleting
    then
      l_values.acc  := :old.acc;
      l_values.value := - :old.value;
    end if;
    l_moves(l_idx) := l_values;
  end before each row;

  after statement is
  begin
    merge into acc a
    using (select acc,
                  sum(value) value
             from table(l_moves)
            group by acc) t
    on (t.acc = a.id)
    when matched then
      update
         set a.current_rest = a.current_rest + t.value;
  end after statement;

end;
/

...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276802
Сергей Арсеньев
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shr,

Так этож фича. :) Триггер не может правильно подсчитать сумму по определению.

Заведи себе вьюшку, можно материализованную, и считай там правильно.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276806
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сергей Арсеньев Так этож фича. :) Триггер не может правильно подсчитать сумму по определению.

Заведи себе вьюшку, можно материализованную, и считай там правильно.Почему не может? При update acc происходит блокировка записи, и следующий (во второй сессии) update пройдет только после коммита первой сессии, т.е. в set current_rest=current_rest+delta будет прочитан current_rest уже измененный первой сессией.

С мат. вью есть некоторые проблемы обратной совместимости - сессия сделавшая insert в acc_move не увидит правильного остатка вплоть до коммита.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276809
Сергей Арсеньев
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ShrПочему не может? При update acc происходит блокировка записи,
Из простого - и при insert тоже. Только вот вторая транзакция твоего insert не заметит.
А commit сделает, например, после первой. Вот и будет у нее сумма не та.
Короче сделай нормальную вьюху и не пытайся обмануть судьбу велосипедом.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276813
Сергей Арсеньев
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ShrС мат. вью есть некоторые проблемы обратной совместимости - сессия сделавшая insert в acc_move не увидит правильного остатка вплоть до коммита.
Так и делай подсчет суммы для транзакции отдельно по окончании первой операции.
Но понимай, что он темпорален и в момент когда ты получил ответ, он уже может быть не верен.
Ну или выстраивай транзакции в очередь блокировками.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276814
Сергей Арсеньев
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сергей Арсеньев,

не "не верен", а "не актуален"
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276819
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сергей АрсеньевИз простого - и при insert тоже. Только вот вторая транзакция твоего insert не заметит.insert в какую таблицу? Если acc, то это будет новая запись остаток в которой будет подсчитываться с нуля и вторая транзакция вообще этот счет не знает, если в acc_move то триггер все равно делает update acc, который вешает блокировку о которой я и говорил.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276823
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
2 Сергей Арсеньев
> Так и делай подсчет суммы для транзакции отдельно по окончании первой операции.
Если бы код писался с нуля - то и вопросов бы не было, но есть уже немало кода в разных клиентах который может быть написан не так, и его придется просматривать и возможно исправлять.

> Ну или выстраивай транзакции в очередь блокировками.
Триггер из первого поста именно это и должен делать, и я не понимаю почему не делает.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276835
Сергей Арсеньев
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shr,

а у тебя merge над acc_move не бывает?
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276842
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В all_source не нашел, но вообще не вижу причин его запрещать.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276868
Сергей Арсеньев
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shr,

Ну это просто вторая часть балета
compound и merge ведут себя не совсем так, как по началу думаешь
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
drop table acc_move;
drop table acc;
drop sequence acc_sq;
create table acc(id integer not null primary key, current_rest number default 0 not null);
create table acc_move(id integer not null primary key, acc integer not null references acc(id), value number not null);
create sequence acc_sq;

CREATE OR REPLACE TRIGGER trg$acc_move_rest
  for insert or update or delete on acc_move
  compound trigger

  n number;

  before statement is
  begin
    dbms_output.put('before: '||nvl(to_char(n),'null'));
    select acc_sq.nextval into n from dual ;
    dbms_output.put_line(' '||nvl(to_char(n),'null'));
  end before statement;

  before each row is
  begin
    if inserting then
     dbms_output.put_line('...insert '||nvl(to_char(n),'null'));
    elsif updating then
     dbms_output.put_line('...update '||nvl(to_char(n),'null'));
    elsif deleting then
     dbms_output.put_line('...delete '||nvl(to_char(n),'null'));
    end if;
  end before each row;

  after statement is
  begin
    dbms_output.put_line('after! '||nvl(to_char(n),'null'));
  end after statement;

end;
/

insert into acc(id) values (1);
set serveroutput on;
insert into acc_move(id,acc,value) values (1,1,1);
merge into acc_move a
 using (select 1 id, 1 acc,2 val from dual
   union all
    select 2 id, 1 acc,4 val from dual
  ) t
 on (a.id=t.id)
  when matched then
      update set a.value = t.val
  when not matched then
      insert (id,acc,value) values (t.id,t.acc,t.val)
;


результат
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
1 row inserted.

before: null 1
...insert 1
after! 1


2 rows merged.

before: null 2
before: null 3
...update 3
...insert null
after! null
after! 3
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276879
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Т.е. при merge before и after statement части триггера срабатывают по два раза. Для исправления достаточно, вроде бы, очистить коллекцию изменений после merge.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276897
Фотография -2-
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Зачем compound?
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276907
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
-2-Зачем compound?Чтобы не создавать дополнительного пакета для хранения коллекции. В этом есть какие-то подводные камни?
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276914
Сергей Арсеньев
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ShrТ.е. при merge before и after statement части триггера срабатывают по два раза. Для исправления достаточно, вроде бы, очистить коллекцию изменений после merge.
Какую. Если ты заметишь, то для insert ее и так нет. :)
Временная табличка не лучше ли будет? ее да очищать придется.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276916
Фотография -2-
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shr-2-Зачем compound?Чтобы не создавать дополнительного пакета для хранения коллекции. В этом есть какие-то подводные камни?Перефразирую. Зачем выносить апдейт из строчного триггера?
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39276929
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сергей АрсеньевКакую. Если ты заметишь, то для insert ее и так нет. :)Упс, слона-то я и не заметил. Думал, глобальная переменная будет вести себя как в пакете - т.е. сохраняться в течение всей сессии (не считая сброса при перекомпиляции).

-2-Зачем выносить апдейт из строчного триггера?Чтобы при массовом изменении acc_move и update acc тоже был массовым, а не построчным. Но вообще, если таких операций немного, то этим можно и пренебречь.
...
Рейтинг: 0 / 0
Подсчет текущего остатка триггером
    #39277070
Shr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Проверил. Когда row level триггер во время merge натыкается на неинициализированную коллекцию то выдает ошибку при попытке её расширить. Т.е. неверных значений это породить не может. И значит ошибка где-то в другом месте.
...
Рейтинг: 0 / 0
18 сообщений из 18, страница 1 из 1
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Подсчет текущего остатка триггером
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]