Выполнение ХП из триггера. Странный результат

Запросы, планы, оптимизация запросов, ...

Модераторы: kdv, CyberMax

Ответить
Pavel_ch
Сообщения: 24
Зарегистрирован: 09 мар 2006, 15:21

Выполнение ХП из триггера. Странный результат

Сообщение Pavel_ch » 09 фев 2008, 20:10

Доброго всем времени суток
Для начала опишу ситуацию
Есть ХП, реализующая начисление абонентской платы абонентам телефонной сети. Она пробегает по всем активным на начало месяца телефонам (используя реквизиты Install_date и Uninstall_date) и по каждому в соответствии с его тарифным планом производит первоначальное начисление базовой абонплаты.
Есть также таблица где регистрируется за какой период уже была начислена абон. плата (там хранится месяц и год всего лишь). При записи каждого звонка а таблицу, срабатвает триггер before insert, в нём происходит обращение к этой таблице и проверяется начислена ли абон. плата за месяц в котором был совершен звонок и если вдруг окажется что АП ещё не была начислена, то выполняется вышеупомянутая ХП и в таблицу закрытых периодов ставится об этом отметка. Всё.
Теперь собстанно ХП:

Код: Выделить всё

CREATE PROCEDURE ABON_PAY_CHARGE (
    per_month smallint,
    per_year smallint,
    last_upd timestamp)
as
declare variable tel_id bigint;
declare variable contr_id bigint;
declare variable tp_code smallint;
declare variable tp_abon_pay numeric(8,2);
declare variable pred_month smallint;
declare variable pred_year smallint;
declare variable last_day date;
begin
  /*Делаем отметку о том что за данный период АП начислена*/
  insert into closed_periods(p_month, p_year) values (:per_month, :per_year);
  /*Получаем год и месяц предыдущего периода, на вход 1,2008 - получим 12,2007*/
  select pred_month,  pred_year from pred_period(:per_month, :per_year) into :pred_month, :pred_year;
  /*получаем последний день предыдущего периода, на вход 12,2007 - получаем 31.12.2007*/
  execute procedure end_of_month(:pred_month, :pred_year) returning_values :last_day;
  for
  /*выбираем все активные по стстоянию на 31.12.2007 телефонные номера..... */
  select telephones.id, telephones.contract_id, telephones.tarif_plan_code,
  tarif_plan.abon_pay from tarif_plan
   inner join telephones on (tarif_plan.id = telephones.tarif_plan_code)
  where
   ((telephones.install_date <= :last_day) and ((telephones.uninstall_date >= :last_day) or (telephones.uninstall_date is null)))
  into :tel_id, :contr_id, :tp_code, :tp_abon_pay
  do
    begin
    /*.....и на каждый из них начисляем базовую абон.плату за Январь 2008*/
    insert into abon_pay(P_month, p_year, id_telephone, id_contract, tarif_plan_code,
    abon_pay_value, abon_pay_result, last_updated) values
    (:per_month, :per_year, :tel_id, :contr_id, :tp_code, :tp_abon_pay, :tp_abon_pay, :last_upd);
    end
end
А вот с её вызовом проблема. Если просто выполнить запрос который находится в блоке for select do между for и select, то он возвращает 1054 записи что соответствует действительности. Если в триггере теперь таким образом выполнить эту ХП (closed_periods это таблица где хранятся данные о том начислена АП или нет)

Код: Выделить всё

if (not exists(select first 1 id from closed_periods where ((p_month = new.c_month) and (p_year = new.c_year)))) then
  execute procedure abon_pay_charge(new.c_month, new.c_year, :last_upd);
то в таблице Abon_pay окажется только 155 записей вместо ожидаемых 1054. А если сделать "в лоб" (некрасиво и нерационально)

Код: Выделить всё

select count(*) from closed_periods where
  ((p_month = new.c_month) and (p_year = new.c_year))
  into :abon_pay_cnt;
  if (abon_pay_cnt = 0) then execute procedure abon_pay_charge(new.c_month, new.c_year, :last_upd);
то все 1054 записи оказываются в нужной таблице.
Может кто подскажет почему так? Вроде и тот способ и другой одну процедуру выполняют, а результат разный

P.S. Firebird 2.0.3

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 11 фев 2008, 09:21

форматирование безобразное, читать тяжело. форматировать надо так:

Код: Выделить всё

...
for select ...
 from ...
 where ...
 into ...
...
select ...
 from ...
а то у тебя не поймешь - for ты куда-то задвинул, то ли это коммент, то ли еще что. Перечень таблиц во from тоже хрен знает где. И т.п.

Кузнецов Евгений
Сообщения: 144
Зарегистрирован: 16 фев 2006, 22:36

Re: Выполнение ХП из триггера. Странный результат

Сообщение Кузнецов Евгений » 11 фев 2008, 11:19

Доброго времени суток!
Pavel_ch писал(а):Она пробегает по всем активным на начало месяца телефонам (используя реквизиты Install_date и Uninstall_date) и по каждому в соответствии с его тарифным планом производит первоначальное начисление базовой абонплаты.
Так, а что делать, если абонент подключился или расторг договор в середине месяца? В Вашем варианте ему либо ничего не выставится, либо выставится сумма за весь месяц.

Код: Выделить всё

  /*выбираем все активные по стстоянию на 31.12.2007 телефонные номера..... */
  select telephones.id, telephones.contract_id, telephones.tarif_plan_code,
  tarif_plan.abon_pay from tarif_plan
   inner join telephones on (tarif_plan.id = telephones.tarif_plan_code)
  where
   ((telephones.install_date <= :last_day) and ((telephones.uninstall_date >= :last_day) or (telephones.uninstall_date is null)))
Вообще ничего не понимаю. Представим, что абонент расторг договор 31.12.2007 - тогда в Вашем запросе он будет считаться действующим и ему выставится АП за январь
Pavel_ch писал(а): Если в триггере теперь таким образом выполнить эту ХП (closed_periods это таблица где хранятся данные о том начислена АП или нет)

Код: Выделить всё

if (not exists(select first 1 id from closed_periods where ((p_month = new.c_month) and (p_year = new.c_year)))) then
  execute procedure abon_pay_charge(new.c_month, new.c_year, :last_upd);
то в таблице Abon_pay окажется только 155 записей вместо ожидаемых 1054.
Не должно быть. Ищите, на какой записи прервалась вставка - может быть, сработало какое-нибудь constraint, и Вы его погасили в триггере через When? first 1 id здесь можно заменить на * - все равно exists выберет только первую запись.

Кузнецов Евгений
Сообщения: 144
Зарегистрирован: 16 фев 2006, 22:36

Re: Выполнение ХП из триггера. Странный результат

Сообщение Кузнецов Евгений » 11 фев 2008, 11:32

Pavel_ch писал(а):P.S. Firebird 2.0.3
Да, кстати, у Вас же раньше IB 7 был. Если Вы эту ХП создавали на IB 7 и не меняли с тех пор, а переходили на 2.03 через backup-restore, то возможна несовместимость BLR - http://www.ibase.ru/devinfo/prevver.htm

Attid
Спец
Сообщения: 377
Зарегистрирован: 14 ноя 2006, 09:58

Сообщение Attid » 11 фев 2008, 12:20

first 1 id здесь можно заменить на * - все равно exists выберет только первую запись.
ну это скорее ни как не повлияет, ну а разве количество полей он не попытается прочесть ?
конечно это наверно лишнее но я предпочитаю select 1 from .. =)

dimitr
Разработчик Firebird
Сообщения: 888
Зарегистрирован: 26 окт 2004, 16:20

Сообщение dimitr » 11 фев 2008, 13:28

Attid писал(а):а разве количество полей он не попытается прочесть ?
запись с диска всегда читается целиком. А кол-во полей сохранено в кеше метаданных. Где ты видишь экономию?

Attid
Спец
Сообщения: 377
Зарегистрирован: 14 ноя 2006, 09:58

Сообщение Attid » 11 фев 2008, 13:59

не вижу вот и спросил =)

а экономия только в человеческом факторе =) ели дать прогам возможность писать * хоть где нибуть, начнут пихать где не лень, поэтому проще запретить везде писать * от греха подальше.

Ответить