Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Подсчет текущего остатка триггером / 18 сообщений из 18, страница 1 из 1
20.07.2016, 08:15:44
    #39276797
Shr
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
20.07.2016, 08:26:24
    #39276802
Сергей Арсеньев
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Подсчет текущего остатка триггером
Shr,

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

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

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

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

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

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

а у тебя merge над acc_move не бывает?
...
Рейтинг: 0 / 0
20.07.2016, 09:39:57
    #39276842
Shr
Shr
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Подсчет текущего остатка триггером
В all_source не нашел, но вообще не вижу причин его запрещать.
...
Рейтинг: 0 / 0
20.07.2016, 10:14:58
    #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
20.07.2016, 10:29:36
    #39276879
Shr
Shr
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Подсчет текущего остатка триггером
Т.е. при merge before и after statement части триггера срабатывают по два раза. Для исправления достаточно, вроде бы, очистить коллекцию изменений после merge.
...
Рейтинг: 0 / 0
20.07.2016, 10:53:02
    #39276897
-2-
-2-
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Подсчет текущего остатка триггером
Зачем compound?
...
Рейтинг: 0 / 0
20.07.2016, 10:59:22
    #39276907
Shr
Shr
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Подсчет текущего остатка триггером
-2-Зачем compound?Чтобы не создавать дополнительного пакета для хранения коллекции. В этом есть какие-то подводные камни?
...
Рейтинг: 0 / 0
20.07.2016, 11:06:55
    #39276914
Сергей Арсеньев
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Подсчет текущего остатка триггером
ShrТ.е. при merge before и after statement части триггера срабатывают по два раза. Для исправления достаточно, вроде бы, очистить коллекцию изменений после merge.
Какую. Если ты заметишь, то для insert ее и так нет. :)
Временная табличка не лучше ли будет? ее да очищать придется.
...
Рейтинг: 0 / 0
20.07.2016, 11:13:18
    #39276916
-2-
-2-
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Подсчет текущего остатка триггером
Shr-2-Зачем compound?Чтобы не создавать дополнительного пакета для хранения коллекции. В этом есть какие-то подводные камни?Перефразирую. Зачем выносить апдейт из строчного триггера?
...
Рейтинг: 0 / 0
20.07.2016, 11:30:11
    #39276929
Shr
Shr
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Подсчет текущего остатка триггером
Сергей АрсеньевКакую. Если ты заметишь, то для insert ее и так нет. :)Упс, слона-то я и не заметил. Думал, глобальная переменная будет вести себя как в пакете - т.е. сохраняться в течение всей сессии (не считая сброса при перекомпиляции).

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


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