TOP

データベースsqlite3 その13

トリガー処理を定義する
売上管理システム2・挿入、更新、削除とトリガー処理

売上管理システム・トリガー処理

その12でとりあえず、売り上げ管理システムのデータベースはできました。但し、個々のデータの変更に連動して特定のデータを変更する場合は、どう したらいいのでしょうか。その解決策はいろいろ考えられます。注文詳細テーブルのデータを書き換えることになるのですが、このテーブルを参照する注文テー ブルの内容も書き換える必要があります。

テーブルにデータを挿入・更新・削除する手順

**動作確認を行う場合は sqlite> . quitを行ってから再度sqlite プロンプトを立ち上げ、pragma foreign_keysを設定しなおして、ステートメントを発行しないと、外部参照、トリガー処理が反映されないことがあります。

売り上げ管理システムでは、データの挿入・更新・削除のできるできないを以下のような方針にしてみました。

insertupdatedelete
buyer tableyes allno all
<id>no
nameno
branch tableyes allno all
<id>no
nameno
goods tableyes allno all
codeno
nameno
priceyes
orders tableyes allno all
idno
buyer_idno
publishno
branch_idno
amountyes
details tableyes allno all
<orders_id>no
<goods_id>no
quantityyes
売り上げ管理システムにおけるデータベースレコードの挿入・更新・削除

購入者情報 buyer テーブル

ドットコマンド操作では、insert update delete は操作権限の指定は出来ない。とはいえ、ここでは、すべてのキーに対して、挿入操作 insert しかしないことを前提とする。

変更が生じた場合は新たに id を発行して挿入する。旧データには削除マークをたてる。

削除が生じた場合は、旧データに削除マークをたてる。

店 branch テーブル

buyer テーブルと同様、挿入操作 insert しかしないことを前提とする。

変更が生じた場合は新たに id を発行して挿入する。旧データには削除マークをたてる。

削除が生じた場合は、旧データに削除マークをたてる。

商品 goods テーブル

商品の価格変更を可能とする。価格変更履歴テーブルを作成しログを残す。変更処理は、当日のorders details データ処理後に行われ、翌日以降に反映されるものとする。

削除 delete が生じた場合は、旧データに削除マークをたてる

注文 orders ・注文詳細 details テーブル

売上伝票を読み込みデータベースにデータを挿入する手順

注文詳細テーブルは注文テーブルの注文番号を参照しているので、以下の順番で読み込むこととします。

  1. 売上伝票の繰り返し箇所を除いたデータを注文テーブルに格納する。
  2. 売上伝票の繰り返し箇所を売り上げ詳細テーブルに格納する。

注文 orders テーブル

合計金額は変更可能とする。これば、対象注文 id に対して売り上げ詳細テーブルにおける変更が生じる可能性があるためです。

また、合計金額は、対象注文 id における注文詳細テーブルデータ読み込み終了時点で、合計金額を算出し、注文テーブルの合計金額と照合できるようにします。照合できれば、注文テーブルのキーとして、照合済みフラグにチェックを入れます。

変更・削除処理が生じた場合は、まず、対象 id の対する、注文詳細テーブルデータの変更・削除処理を行った後に、変更・削除マークを立てる。

変更削除処理はテーブルにデータを挿入した日時、つまり当日のみ有効とします。

注文詳細 details テーブル

注文詳細テーブルのデータ挿入は注文テーブルデータ挿入と連動しております。ここでは、注文テーブルデータの合計金額を照合する為、売上伝票の繰り返し部分のデータが終了するタイミングで指定注文 id の注文詳細テーブルデータの合計金額を計算する必要があります。このことから、コントロールブレークのタイミングを示すフラグキーを追加することになります。

変更・削除処理が生じた場合は、まず、対象 id の対する、注文詳細テーブルデータの変更・削除処理を行った後に、該当注文テーブルデータに変更・削除マークを立てる。注文詳細削除・変更履歴テーブルを生成し、注文 id 、商品 id 、増減数、処理日時を格納する。

挿入・変更:削除処理におけるフラグの表示

購入者情報テーブル・店テーブル・商品テーブルなどのレコードを削除したい場合は、sql 文の delete による処理をしないで、削除を示すフラグが必要になってきます。

注文テーブル・詳細テーブルにおいては、挿入・変更・削除を示すフラグを示す列を追加します。その意味合いについては、以下のように定義します。

  • データ入力処理(ここではコンソールからの入力でありますが)によりレコードが入力され、その内、合計金額が引き続き入力される詳細テーブルのコ ントロールブレークを示すフラグを含むデータが入力された時点で合計金額の照合を行います。照合が合えば、挿入を示すフラグを立てます。照合ができなけれ ば、注文テーブルレコードにおいて、注文データ、詳細データどちらかに不備があるとみなし、挿入フラグが挿入されません。
  • 変更・削除のフラグは、注文テーブル・詳細テーブル双方に挿入されます。
  • 詳細テーブルへのレーコードの新たな追加は、新たな注文が生成されたとして処理するものとします。したがって、挿入フラグは存在しないとします。

テーブルの列の追加[ALTER TABLE][DEFAULT][CHECK]

ALTER TABLE table_name ADD COLUMN column_def...;
/*
  追加したカラムはテーブルの最後に追加されます。
  カラムを追加する場合は次の条件を満たしていなければなりません。
  1. PRIMARY KEY や UNIQUE 制約は設定できない
  2. DEFAULT 制約を設定する時は、CURRENT_TIME/CURRENT_DATE/CURRENT_TIMESTAMP は指定できない
  3. NOT NULL 制約を設定する時は、NULL以外のデフォルト値の設定が必要
+/
CREATE TABLE table_name(
column datatype DEFALT value ....);
/*
 INSERT INTO ステートメントが特定の値を提供しない場合に、列にデフォルト値を提供します。
*/
CREATE TABLE table_name(column datatype check(expression) ....);
/*
 入力される値を条件でチェックできます。条件が false と評価された場合、レコードは制約に違反しているため、テーブルに入力されません。
*/

上記で示した方針の下、以下のように列項目を追加します。

buyer テーブルに列・列制約を追加する

削除するフラグを示す列 del_flag を追加する。削除する場合は、flag=1 または flag=true とする。

  1. DEFAULT 制約により、追加された del_flag 列に対するデフォルト値である0が格納されている。
  2. 新たに挿入する flag 列データは、null でも挿入できてしまうので、null を使うべきではない。
  3. デフォルト値が設定されているが、true 、false 、0 、1 のいずれかを入れないと格納されないようである。または、「insert into buyer (name) values (‘yamamoto’)」というように記述すれば、意図した格納が実現できる。

branch テーブルに列・列制約を追加する

削除するフラグを示す列 del_flag を追加する。削除する場合は、del_flag=1 または del_flag=true とする。

goods テーブルに列・列制約を追加する

削除するフラグを示す列 flag を追加する。削除する場合は、flag=1 または flag=true とする。

orders テーブルに列・列制約を追加する

orders テーブルへの 合計金額照合確定処理、その後の変更処理、削除処理に対して、処理があったことを示す列 iud_mark を追加することとします。照合確定を i 、変更を u 、削除を dとします。

details テーブルに列・列制約を追加する

売上伝票の繰り返し部分の読み込み終了のタイミングで、該当注文 id の商品合計金額計算をするので、コントロールブレークのタイミングを示すマーク c_mark の列を追加します。

さらに、orders テーブルと同様、iud_mark も追加することにします。また、単価計算結果であるsubtotal列も追加します。

テーブルデータ変更履歴テーブル作成

価格変更履歴テーブル chglog_price作成

  1. default 句では関数は使えません。定数のみを指定する必要があります。

注文詳細削除・変更履歴テーブル chglog_details作成

トリガー[CREATE TRIGGER]

トリガーとは、テーブルに対してある処理(挿入、更新、または削除)が行なわれると、あらかじめ設定した処理を自動的に実行する仕組みの事です。ここではテーブルの購入・削除の履歴を残す事、金額合計計算し、照合・格納に使用します。

CREATE TRIGGER trigger_name [BEFORE|AFTER] INSERT ON table name  or
CREATE TRIGGER trigger_name [BEFORE|AFTER] DELETE ON table name  or
CREATE TRIGGER trigger_name [BEFORE|AFTER] UPDATE OF column name ON table name
BEGIN
 -- Trigger logic goes here....
END;
/*
  テーブルに対する処理が行なわれるWHERE句直前と直後の値は、「OLD.カラム名」「NEW.カラム名」で取得できます。
insert trigger の場合 new.列名
update trigger の場合 old.列名 new.列名
delete trigger の場合 old.列名
 として利用できます。
DROP TRIGGER trigger_name;  トリガー解除
  WHEN 句が指定されている場合、指定された SQL ステートメントは、WHEN 句が真である行に対してのみ実行されます。WHEN 句が指定されていない場合、SQL ステートメントはすべての行に対して実行されます。
  変更するテーブルは、トリガーが関連付けられているテーブルまたはビューと同じデータベースに存在する必要があり
*/

商品 goods テーブル価格変更履歴テーブルにデータを格納する

注文テーブルの日時列 publish の妥当性をチェックする

sqlite3 において、日時データはここでは yyyy-mm-dd で格納される事を前提としております。ストレージタイプでは text 格納なのですが、フォーマットされた状態で格納する必要があります。このことをチェックする仕組みは sqlite3 では用意されていないので、自力で作らなければいけないようです。CREATE 文の CHECK 制約を使うことも考えられますが、不十分なものになってしまいます。ここでは、トリガーを使って実現してみたいと思います。insertトリガーと updateトリガーに対して、同じようにチェック処理を行っています。

  1. 日時データとして認識されない場合は不正とみなし、ロールバック処理をする。
  2. 日付データが’31’の場合は、月データが’02’, ’04’, ’06’, ’09’, ’11’の場合は不正とみなし、ロールバック処理をする。
  3. 日付データが’30’の場合は、月データが’02’であれば、不正とみなし、ロールバック処理をする。
  4. ,日付データが’29’、月データが’02’の場合は、うるう年判定を行い、うるう年でなければ、不正とみなし、ロールバックする。

「データベース sqlite3 その9」に詳しく記述しております。

注文詳細テーブル追加処理における単価計算と注文テーブルの合計金額の照合

注文詳細テーブルにレコードを追加するたびごとに、その単価計算結果をsubtotal列に格納します。c_mark列にtrueがセットされてい たら、該当注文テーブルコードの最終の追加処理とみなし、その合計金額を計算し、注文テーブルのamount列データと照合します。マッチしていたら、注 文テーブルのiud_mark列に’i’を格納します。

注文した商品を追加する

details テーブルにおいて対象注文 id の商品として新たに挿入する。 その際、iud_mark 列は ‘i’ にセットする。

  • 該当 orders テーブル id レコードの iud_mark 列に ‘u’ をセットされる。
  • 注文当日であるかチェックする。
  • 合計金額 amount の再計算をする。
  • details テーブルに挿入したレコードの c_mark 列は true にセットされる。

注文した商品の数量を変更する

details テーブルにおいて対象注文 id の商品の iud_mark 列を’u’ にして更新する。

  • 注文当日であるかチェックする。
  • 該当注文テーブル id レコードの iud_mark 列に ‘u’ をセットする。
  • 合計金額 amount の再計算をする。

注文した商品の一部を削除する

details テーブルにおいて対象注文 id の商品の iud_mark 列に ‘d’ をセットする。

  • 該当注文テーブル id レコードの iud_mark 列に ‘u’ をセットする。
  • 注文当日であるかチェックする。
  • 合計金額 amount の再計算をする。

注文を削除する

orders テーブルの対象 id レコードの iud_mark 列に ‘d’ をセットする。

  • details テーブルの対象 orders_id の iud_mark列に ‘d+’ をセットする。
  • 注文当日であるかチェックする。
  • orders.amount details.subtotal の値はそのまま残す。

当日以外の変更・削除は、新たに orders id を発行し、削除の場合は details subtotal あるいは、orders amount にマイナスの数字を入れることにします。

トリガーによらないもの

コマンドラインからのデータベース処理にはそぐわないものがあります。

  • 入力処理に係わる、表示処理
  • 出力処理に係わる、表示処理
  • コード設計による、データのバリデーション。
  • セキュリティ処理
  • データの分析処理

システムを動かす為に、実際、プログラミング言語を使ってデータベースを操作しています。極端なことを言えば、トリガー処理がなくても、目的とする ことは実現できます。但し、最大のメリットは、処理系に依存しないことです。ドライバーさえ導入できれば、さまざまなプログラミング言語で利用できます。 各プログラミング言語の仕様を踏まえた上で、上記の処理をあわせて作り上げていくことになるでしょう。

トリガー処理と外部キー制約

これまで作り上げてきた「売り上げ管理システムのデータベース」には、トリガー処理と外部キー制約の両方が記述されています。どちらも、sqlス テートメント実行時に自ずと実行されるものです。実は、この両者は片方ずつでないとうまく動作しないようです。その動作を確認してみることにします。

トリガー処理を生かすには、外部キー制約を解除しておく必要があります。逆に、外部キー処理を使う為には、外部キー処理を有効に し、トリガー処理と共存しないようにします。アプリケーションの中からデータベースを利用する場合は、トリガー処理と同様の動作をアプリケーション側で作 る必要があるようです。

外部キー制約と同じようなトリガー処理の実現

ここでは、存在しない親キーを指定した場合は、エラーを出すトリガーを実装してみます。

まずは外部キーを確認してみます。

detailテーブルにおける外部キー制約に代わるトリガー生成

  • 存在しないgoodsテーブルのgoods_id値におけるレコードを指定した場合はエラーをだす。
  • 削除マークのあるgoodsテーブルのgoods_id値におけるレコードを指定した場合はエラーをだす。
  • 存在しないordersテーブルのid値におけるレコードを指定した場合はエラーをだす。

ordersテーブルにおける外部キー制約に代わるトリガー生成

  • 存在しないbranchテーブルのbranch_id値におけるレコードを指定した場合はエラーをだす。
  • 存在しないbuyerテーブルのbuyer_id値におけるレコードを指定した場合はエラーをだす。
  • 削除マークdel_flag列がtrueであればエラーを出す。

尚、上記の処理は、check()関数でも実現できるようです。

参考:【初心者向け】SQLiteのForeign Key Constraintsを徹底解説

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください