履歴を取ってみよう
一般的に、コンピュータに格納されるファイルは、その属性として「作成日時」および「更新日時」の値を保持しています。 そこからの連想なのでしょうが、データベース設計においても、重要なデータを格納するテーブルにこれらの値を格納するカラムが設けられていることが少なくありません。 例えば、次のように定義されたテーブルを見たことはないでしょうか。
カラム | 型 | 制約 | 解説 |
---|---|---|---|
id | INTEGER | PRIMARY KEY | 識別子 |
subject | TEXT | NOT NULL | 表題 |
content | TEXT | NOT NULL | 本文 |
disabled | INTEGER | NOT NULL |
状態 [ 0: 有効, 1: 無効, 2: 削除 ] |
time_register | TIMESTAMP | NOT NULL | 登録日時 |
time_update | TIMESTAMP | NOT NULL | 更新日時 |
けれども、この「更新日時」が実用にどれほど役に立つものかを考えてみると、その効果のほどは甚だ疑問です。 何故なら、このフィールドはあくまで「最終の」更新日時を表すものに過ぎず、次の更新が生じたときに、その値は上書きされてしまうもの。 その最終の更新についてさえ、「いつ」行われたかは分かるものの、「どのような」変更がなされたかについては、何の情報も提供されません。 そうしたことを考えると、この「更新日時」のフィールドは殆ど意味を持たないものだと言えます。
話は変わりますが、Mac OS においてはファイルへのアクセス日時が最終のものだけでなくすべてが記録される模様。 これは便利と言えば便利なのかもしれませんが、恐ろしい気もしますね。
http://takagi-hiromitsu.jp/diary/20090704.html
閑話休題。 こうした事情から、より重要なデータを扱うシステムでは任意の時点におけるレコードの状態を参照できるようにするため「履歴」の情報を含めてデータベースに格納する手法が採られます。 しかしながら、この履歴を取る方法は、作り手によってまちまち。 特に、記録の対象を重要なフィールドに限定したり、変更の差分だけを格納するテクニックを駆使したりし始めると、データベースの構造とそれを処理するコードは急速にその複雑さを増し、保守運用の困難なシロモノと化す傾向があります。
そこで今回は、この履歴をとるために私が用いているごく簡単な手法を紹介したいと思います。
発想の転換
そもそも、履歴を取るシステムの設計が複雑化する原因は、履歴の情報を元のレコードとは別のテーブルに格納しようとすること、言い換えれば、履歴情報のためにわざわざ新しいデータ構造を導入することにあるのではないでしょうか。 履歴の情報のデータ型は、その性質から必然的に、元レコードの型のサブセット (部分集合) となります。 従って、そのスーパセット (上位集合)、即ちもとのレコード型よりも大きくなることは原則としてありません。 殆どのシステムにおいて、履歴機能に対して、元レコードの情報の完全な再現が要求されることを考え併せるならば、履歴も元レコードも同じテーブルに格納するのが良さそうに思えます。 というわけで、最初に挙げたテーブル message の定義を次のように変更してみます。
カラム | 型 | 制約 | 解説 |
---|---|---|---|
id | INTEGER | PRIMARY KEY | 識別子 |
history | INTEGER | NOT NULL |
オリジナルレコード識別子 (値 0 は自信がオリジナルであることを表す。) |
subject | TEXT | NOT NULL | 表題 |
content | TEXT | NOT NULL | 本文 |
disabled | INTEGER | NOT NULL |
状態 [ 0: 有効, 1: 無効, 2: 削除 ] |
time_register | TIMESTAMP | NOT NULL | 登録日時 |
更新日時を表すカラム time_update がなくなり、新たに history という整数型のカラムが付け加えられています。 これでどのように履歴を表すのか、簡単な例を挙げてみましょう。
id | history | subject | content | disabled | time_register |
---|---|---|---|---|---|
23 | 0 | 保険料の納付手続きについて | 2/14 (火) に、以前お渡しした調査シートを回収します。 | 0 | 2012-01-31 13:30:41 |
26 | 23 | 保健料の納付手続きについて | 2/14 (月) に、以前お渡しした調査シートを回収します。 | 0 | 2012-02-01 09:02:11 |
27 | 23 | 保険料の納付手続きについて | 2/14 (月) に、以前お渡しした調査シートを回収します。 | 0 | 2012-02-03 15:58:20 |
1月31日の13:30に登録したメッセージについて、2月1日の09:02に表題の誤字を修正 (保健 → 保険)、さらに2月3日の15:58に本文中の曜日を訂正 (月 → 火) したことが読み取れます。 これらの変化を視覚的に把握するため、各レコードが「最新」データとして扱われる期間を時間軸上にプロットしてみました。
履歴レコードが「最新」データとなるのは、その前の履歴レコード (これが存在しない場合はオリジナルレコード) の「登録日時」から、自身の「登録日時」までの期間。 一方、オリジナルレコードを「最新」データとして扱うことができるのは、最も新しい履歴の「登録日時」 (すなわち「最終更新日時」) から現在に至るまでです。 一見するとやや複雑なルールに思えるかもしれませんが、少し検討すれば、これ以上削ることが難しい、シンプルな構造であることを理解してもらえると思います。
できるだけ単純に
システム開発とは、言い換えるならば、増大する複雑さとの戦いです。 従って、データベースに限らず、設計においてもっとも重視すべきことは、その構造を可能な限り単純に保つこと。 (Keep It Simple Stupid!) 「言うは易し、行うは難し」な設計指針ですが、これを実践できるか否かが、設計者 (ソフトウェア・デザイナ) としての力量を差を決定的に分ける境界線です。
コメント