概要
データベースの設計や不具合の調査をしていて、ふと思ったことは無いでしょうか?
ログやバックアップに頼らずに、データベースに全ての変更履歴が残るようにできないだろうか、と。
試しに、設計してみるとFOREIGN KEY制約を保ちながら履歴を持ったテーブルを設計するというのは、なかなか厄介なことに気づきます。
しかし、突飛なアイディアという訳ではなく、ネット検索してみると結構ヒットします。
変更履歴を持つテーブルの設計
履歴テーブルからデータを取得するSQL
リレーショナルデータベースでは履歴の管理をすべきでない?
データベース設計 ~ マスタデータを含めて、全ての履歴を残したいという要望
先人の試行錯誤を自分なりに咀嚼して、設計してみた方法を文章として残して置こうかと思います。(※実際のプロジェクトに適用してみた気付きを加筆しました)
設計方針
- 履歴を持つことに意味のあるテーブルのみを対象とする。全てのテーブルの履歴を残そうと思わないこと。
- 履歴を持ったテーブルを扱うとディスク容量増加やレコードが増えることによるパフォーマンス低下が発生します。そして、コーディングが複雑になるという問題もあるため履歴が必要不可欠なテーブルに限定します。
- ただし、クラウドでの開発であれば、容量もパフォーマンスもプロジェクト規模に合わせて変更できるため制御は可能だと思いますし、プログラムの複雑さも共通処理にまとめることで、緩和は可能です。
- 履歴には、2種類の考え方がある。
- (1)履歴を参照するテーブルの過去情報が、変更されるタイプ(商品名を変更したら過去のトランザクションテーブルの商品名も変更になる)
- (2)履歴を参照するテーブルの過去情報が、変更されないタイプ(商品名を変更しても過去のトランザクションテーブルの商品名は、取引した時の商品名のままにする)
- ※上記2つのアプローチの違いは、テーブル構造上は、小さな変更で対応可能です。
- 履歴情報だけを別テーブルへ分離する。
- 絶対に変更されない情報を持ったテーブルAと履歴情報だけを持ったテーブルBの2つをワンセットで扱います。
- 2テーブル1セットな構造となるためコーディングの負担を軽減するためViewを使って、纏めます。
- ※EntityFramework Coreは、Viewに対応してないので、私は使っていませんが、使える環境の方は使ってみてください
var result = db.EmployeeHis.Where(o => o.EmployeeMstId == employeeId).OrderBy(o => o.EmployeeHisId).Last();
最新情報のリストを取得
最新のリストを取得するのは、少し複雑です。
var employees = db.EmployeeMst.Where(o => o.DelFlag == false)
.Join(db.EmployeeHis, inf => inf.EmployeeMstId, his => his.EmployeeMstId, (inf, his) => his)
.GroupBy(o => o.EmployeeMstId)
.Select(o => new { EmployeeHisId = o.Max(row => row.EmployeeHisId) })
.Join(db.EmployeeHis, maxid => maxid.EmployeeHisId, his => his.EmployeeHisId, (maxid, his) => his);
ImportedLinqを利用した方法
using ImportedLinq;
var employees = db.EmployeeMst.Where(o => o.DelFlag == false)
.Join(db.EmployeeHis, inf => inf.EmployeeMstId, his => his.EmployeeMstId, (inf, his) => his)
.GroupBy(o => o.EmployeeMstId)
.Select(g => g.MaxBy(o => o.EmployeeHisId).First()));