2013年8月29日木曜日

SQL Trigger ではまった

今回のバグは難解でした。Sybase SQL Anywhere のデータベースのバージョンをアップしたら、なんか、変なところでエラーになりました。

伝票 テーブル名 SLIP のレコード新規作成で insert 文を発行しているのですが、別の帳票を印刷すると、レコードの挿入に失敗してしまいます。
帳票のコード中で、現象を引き起こす部分を特定したところ、全く関係のない 日別伝票集計 テーブル名 DSLIP を SELECT * FROM DSLIP WHERE RDATE='2013/8/29'; という極めてシンプルなクエリーをオープンして、クローズするだけで発生していました。
訳がわからなくて、コンポーネントを別にしてみたり、接続をクローズしてオープンしてみたり、SELECT文末に FOR READ ONLY を加えてみたりしましたが、エラーは直りません。もうお手上げに近い感じですが、ふとトリガー?というキーワードが浮かびました。それでも原因がわからなくて、助っ人を頼んで原因を特定できました。
よくある事ですが、日別で伝票番号をシーケンシャルで発生させたいという要望から、DSLIP 中に伝票番号の最大値を持たせて SLIP の INSERT TRIGGER で伝票番号を発生させていました。
問題のトリガーは、こんな感じ
ALTER TRIGGER "insert_slip" before insert order 1
on "DBA".SLIP
referencing new as new_val
for each row
begin
  declare AINC smallint;
  declare thisDSlip cursor for select SCNT from DSLIP where RDATE=new_val.RDATE;
  -- %IF CURRENT REMOTE USER IS NULL THEN
  if new_val.SLIPCD is null then
    if not exists(select* from DSLIP where RDATE=new_val.RDATE) then
      insert into DSLIP(RDATE,LDATE) values(new_val.RDATE,TODAY(*))
    end if
    ;
    open thisDSlip;
    fetch first thisDSlip into AINC for update;
    set new_val.SLIPCD=AINC;
    update DSLIP set SCNT=AINC+1,LDATE=TODAY(*) where current of thisDSlip;
    close thisDSlip
  else
    if not exists(select* from DSLIP where RDATE=new_val.RDATE) then
      insert into DSLIP(RDATE,LDATE,SCNT) values(new_val.RDATE,TODAY(*),new_val.SLIPCD)
    end if
    ;
    open thisDSlip;
    fetch first thisDSlip into AINC for update;
    if AINC<new_val.SLIPCD then
      set AINC=new_val.SLIPCD;
      update DSLIP set SCNT=AINC+1,LDATE=TODAY(*) where current of thisDSlip
    end if
    ;
    close thisDSlip
  end if
  ;
  if(ISNULL(new_val.RENT,0)<>0) or(ISNULL(new_val.EMFLG,0)<>0) then
    set new_val.ACFLG=5
  end if
  -- %END IF;
end;

はい、cursor 宣言がまずくて
  declare thisDSlip cursor for select SCNT from DSLIP where RDATE=new_val.RDATE FOR UPDATE;
というように更新をかけるタイプのカーソルですよと、FOR UPDATE 句が必要でした。これが抜けていたために、データベースの状況により読み取り専用カーソルに対して更新をかけるという事態が発生し、エラーを引き押していたと。

0 件のコメント: