データウィンドウによる更新のしくみ

DWでの更新のしくみ

お疲れ様です。 エイタです。

いきなりですが、PowerBuilder といえばデータウィンドウ。 データウィンドウといえば Update 関数ひとつを呼び出すだけで Update、Insert、Delete のすべてを自動でデータベースに発行してくれるという超強力な更新機能が備わっています。

データウィンドウ上で行われたユーザーの操作により、検索したデータを変更したら UPDATE 文、新しい行を追加したら INSERT 文、行を削除したら DELETE 文がそれぞれ発行されるのです。

果たしてどのようなしくみで判定されてるのか? そのしくみを理解することで、思い通りにデータを扱うことができるかも?

ってなわけで、今回は「データウィンドウによる更新のしくみ」について解説したいと思います!


更新にかかわる要素

Update 関数が内部処理で UPDATE / INSERT / DELETE 文を生成するしくみには複数の要素が絡んでいます。

バッファ

バッファは削除やフィルタリングされたデータ、またはそれ以外のデータを分けて保持するための領域です。 たとえば DeleteRow 関数により削除された行は画面から見えなくなりますが、内部では Delete バッファに保持されている状態であり、元に戻すことも可能です。 バッファの種類は以下のとおり。

  • Primary
    • 画面上に見えているデータが存在するバッファ
  • Delete
    • DeleteRow 関数で削除されたり、RowsMove、RowsCopy 関数でターゲットバッファに Delete! を指定して移動されたデータ
  • Filter
    • Filter 関数でフィルタリングされたり、RowsMove、RowsCopy 関数でターゲットバッファに Filter! を指定して移動されたデータ

 

ステータス

ステータスはそのデータに対して変更が行われたか、または新しく追加された行なのかという現在の状態を保持しています。

  • New
    • InsertRow により追加されたばかりの行
  • NewModified
    • New の行に対して値が入力された状態
  • NotModified
    • Retrieve により検索された直後の行で、値の変更はされていない状態
  • DataModified
    • NotModified の行に対してデータウィンドウ上で値が変更された行

 

また、Update 関数を呼び出した際にどのテーブルに対して更新を行うか、どのカラムを更新の対象にするかといった情報を設定する更新特性も更新 SQL の生成にかかわっています。


SQL 生成のしくみ

データウィンドウではバッファとステータスの状態によって、各行に対してどのような処理を行うかが決まります。

下の図はデータウィンドウで検索したデータに対し、ユーザーが削除や修正といった操作を行った場合にバッファとステータスがどのように変わるかを示したものです。

更新のしくみのイメージ

まずデータの追加についてです。 検索された直後はすべてのデータが Primary バッファに格納されます。 「修正後の DW」を見ていただくと行 9、10 が追加されていますね。 これらはユーザーの操作 (InsertRow) により追加された行です。 追加して値が入力された行のステータスは NewModified となり、入力された値をもとに INSERT 文が発行されます。

図にはありませんが、「追加後に値が入力されなかった行」についてはステータスが New となり SQL は発行されず DB に変更はありません。

次に削除です。 Retrieve により検索されたデータが DeleteRow や RowsMove によって Delete バッファに移動された場合、ステータスに関係なく DELETE 文が発行されます。 ただし InsertRow により検索後に追加された行については、もともとそんなデータは DB には存在しないため何もなかったことになります。

最後に更新です。検索されたデータ、つまり DB に存在するデータをデータウィンドウ上で変更すると、その行のステータスは DataModified となります。 このステータスのデータは変更された項目に対する UPDATE 文が発行されます。

なお、Filter バッファのデータについては Primary バッファと同じ動作になります。

バッファとステータスの組み合わせにより生成される SQL の結果をまとめると・・・

  • Delete バッファに移動された行は DELETE 文
  • Delete 以外のバッファではステータスが NewModified の行は INSERT 文、DataModified の行は UPDATE 文
  • バッファにかかわらず、ステータスが New または NotModified の行に対しては何も行われない

排他制御のしくみ

データウィンドウによる更新処理では標準で論理的排他制御が行われます。

排他制御の方式としては、「データウィンドウで検索された直後のデータと現在のデータベースの値が一致しない場合は他のユーザーによって変更されたと判断し、更新は行わない」というものです。

もう少し詳しく言うと、データウィンドウは検索された直後のオリジナルの値を保持しており、更新や削除を行うための UPDATE 文や DELETE 文を生成する際に、そのカラムの値を条件としてWHERE 句に追加します。 つまり元の値と同じデータを探して更新するということ。 これによりデータウィンドウで検索された後に他のユーザによって値が変更された場合には、更新・削除する対象のデータが見つからず、テーブルへの変更は行われないというわけ。

条件として追加されるカラムは必ずしもすべてのカラムではありません。「更新特性の指定」のUpdate/Delete 文の Where 句の設定により条件が変わります。

更新特性の指定

「Update/Delete 文の Where 句」の設定による動作の違いは下記のとおり。

  • キーカラムのみ
    • 条件はキーカラムのみになります。 他のカラムが他のユーザーに変更されていたとしても更新されます。
  • キーカラムと更新可能カラム
    • キーカラムと「更新可能なカラム」で選択されているカラムが条件となります。
  • キーカラムと修正したカラム
    • キーカラムと「更新可能なカラム」で選択されているカラムのうち、データウィンドウ上で変更されたカラムが条件となります。

 

繰り返しになりますが、上記の対象となったカラムの「検索直後の値」を条件として SQL が発行されます。 これらのカラムが他のユーザーによって変更されている場合、UPDATE または DELETE に失敗し「検索と更新の間に行が変更されました」というエラーが発生します。

さて更新の基本がわかったところで、次はステータスの変更を利用した便利な更新方法をご紹介しましょう。


【応用】更新のたびに履歴データを残す

「データを 1 行検索 → ユーザーによる編集 → 元のデータはそのまま履歴に残し、新しいデータとして登録」という処理を行います。 データウィンドウのステータスを意図的に変更することでこのような処理も簡単に実装することができます。

対象データのテーブルには履歴を管理するための version というキー項目があるという前提。 version は更新するたびに増えていき、この項目の数値が最も大きいものが最新データという仕様だとします。

履歴データのイメージ

id ごとに version が最大のデータが最新とします

データウィンドウで検索するのは、指定された id のうち version が最大のデータという条件です。

1 件のデータを検索し、ユーザーによって変更されたとします。 大まかな更新までの処理の流れはこんな感じ。

  • version を 1 加算する
  • 行のステータスを NewModified に変更する
  • Update 関数を呼び出す

これだけ。 もちろんエラーチェックとかは事前に済ませている、ということで・・・。

1 は最新データとして登録するので version をインクリメントします。 このまま更新してしまうと検索されたデータが上書きされるだけですが・・・。

2 でステータスを NewModified にします。 検索後に編集されたデータは DataModified なので UPDATE 文が発行されるのですが、これを NewModified に変更してしまうことで、こいつは検索されたデータではなく新しく追加されたデータということにしてしまうんですね。 これで INSERT 文が発行されることになります。

ステータスの変更は SetItemStatus 関数を利用します。 変更したい行と目的のステータスを指定します。

integer dwcontrol.SetItemStatus ( long row, integer column, dwbuffer dwbuffer, dwitemstatus status )
integer dwcontrol.SetItemStatus ( long row, string column, dwbuffer dwbuffer, dwitemstatus status )

実はステータスはカラム単位でも保持されていて、カラムごとにステータスを変更することもできるんですが、更新の制御としては行全体のステータスを変更することが多いかと思います。 行全体のステータスを変更する場合はカラムの指定 (第 2 引数) に 0 を渡します。

また、ステータスによっては目的のステータスに直接変更できないものもあります。 たとえば NewModified から NotModified への直接の変更はできません。

下記の表は目的のステータスに変更するために、どのステータスを指定するかを表しています。

目的のステータス
New NewModified NotModifed DataModified
元のステータス
New NewModified! × DataModified!
NewModified NotModifed! × DataModified!
NotModifed New! NewModified! DataModified!
DataModified × NewModified! NotModifed!

× は変更不可です。このような変更を行うためには一度別のステータスに変更してから目的のステータスへと二段階右折をする必要があります。

話がそれてしまいましたが、後は更新するだけ (3) です。 NewModified にステータスを変更したので、もはやこの行は新しく追加された行となっています。 Update 関数により INSERT 文が発行され、元のデータには影響なく新しいデータが追加されます。

ここまでの処理のサンプルです。

long ll_row, ll_rowcount
integer li_version

// 入力値を確定
dw_1.AcceptText()

/*
  エラーチェック
*/

// 更新処理
IF dw_1.GetitemStatus(1, 0, Primary!) = DataModified! THEN

    // version をインクリメント
    li_version = dw_1.GetItemNumber(1, "version")
    dw_1.SetItem(1, "version", li_version + 1)

    // 行のステータスを NewModified に変更
    dw_1.SetItemStatus(1, 0, Primary!, NewModified!)

    // 更新
    IF dw_1.Update() = 1 THEN
        COMMIT;
        MessageBox("情報", "正常に更新しました。")
    ELSE
        ROLLBACK;
        MessageBox("エラー", "更新処理に失敗しました。")
    END IF

ELSE
    MessageBox("情報", "データが変更されていません。")
END IF

これで更新するたびに履歴を残す処理を実装できました。 やっていることはキーの値とステータスを変更するくらいです。 結構簡単でしょ?


まとめ

データウィンドウによる更新のしくみ、なんとなくご理解いただけたでしょうか? 便利ゆえに奥が深いデータウィンドウですが、ステータスとバッファの関係を理解してしまえば更新のイメージがすんなり頭に浮かべられるようになるかと思います。

今回ご紹介したテクニック以外にも、ステータスとバッファを利用して便利な機能を実装できるかもしれません。 もしオシャレなテクニックを思いついたら 公式 Twitter までお寄せください!

ちなみに、データウィンドウについては他にもテクニカルブログに 初心者の憂鬱:DataWindowから送信されているSQLはどうなっているの? とか、データウィンドウが表示されるまで(前編) / (後編) といった記事もあるので要チェック。

そもそもデータウィンドウってなによ、って方はマイグレーション特集ページの 一度使うとやめられない、高生産性の要であるDataWindowの持ち味 をご覧ください!

以上、エイタでした!

テクニカルブログ 一覧を見る
PowerBuilder マイグレーション
PowerBuilderとは? ~デキる大人へ変身できる?ローコード開発ツール~