論理削除がデータを汚している

ベクトルの違うデータ

まあ、それは事実。
ただ、履歴をそのまま残したいということも事実。
いちいち削除履歴テーブルなんて作ってられないのも事実。

# ここでの論理削除は、復活する論理削除じゃなく、
# 物理削除の代わりとして履歴のための論理削除を指します。
# (復活する論理削除って、そもそも削除とは言えないって気も...)

本来、論理削除されたデータって、
そのテーブルの定義するデータとはベクトルの違うデータ
である考えます。

でも、わざわざ削除されたデータを保持するテーブルを作ると、
それはそれで面倒なのでそのまま同じテーブルに持ったままにする。
その方が扱いが簡単なことが多いから。
削除フラグを true にするだけで済むから。

個人的には、業務上重要なテーブルに関しては、
しっかりと「削除履歴テーブル」を用意して、
本体のテーブルには常に有効なデータだけがある状態の方が、
データメンテもプログラムも遥かに楽になりますし、
なんといってもデータが安全です。
テーブルを限定すればそんなに数も多くならないし。

ユニーク制約とのトレードオフ

ポイントは、ユニーク制約が貼れるかどうか

FOOとBARカラムで業務上ユニークなテーブルがあるとして、
でも、そのテーブルに削除フラグ(DEL_FLG)があったら、
少なくともFOOとBARの複合ユニークは貼れません。
(削除も含めて、FOOとBARはユニークって仕様ならいいですが、
まあ、あんまりそういうことはないかなぁと)

FOOとBARと「削除フラグ=false」でユニークになります。
でも、FOOとBARと「DEL_FLG=true」ではユニークにならない。
なので、削除フラグをユニーク制約に含めることはできない。
「FOO=A, BAR=B, DEL_FLG=true」
のデータが大量にあるかもしれないから

ユニーク制約がないと何が困るかって、
一言でずばり「なんでも入っちゃう」ってところ。
要は「FOO=A, BAR=B, DEL_FLG=false」も、
複数入ってしまう可能性があるってこと。

そりゃ、アプリで一生懸命そうならないようにするわけですが、
実際の業務ではこんな単純じゃないわけで、
FOO, BAR, BAZ, QUX, QUUX, CORGEってカラム多かったり、
例えば、FOOの値によってユニーク性が若干変わったり、
そのテーブルに登録するアプリ(アクター)が複数あったり。

とあるすれ違いで入ってしまう可能性はなきにしもあらずです。
ふとしたときに重複データが入ってしまって、
「一件検索が二件になってエラーになっちゃった」とか。
もちろん、しょっちゅうじゃないけど、
でも確かにそういうトラブルは世の中発生しています。

スピード重視の今の時代、その辺の厳密なチェック実装に時間を
なかなかかけてられない、っていうならなおさら。
逆に言うと、少ない労力の仕組みで最低限の担保ができればうれしい。
ちゃんとみっちり仕様考えてテストすれば(お金かければ)OKでしょ、
っていう考えが通じる現場は徐々に少なくなってきています。
テストも大事ですが、一方で仕組みによる防御を加えることも大事。

エラーで困るってだけでなく、そのあとのデータメンテも大変で、
同じ落ちるにしても、せめてデータがブロックされていれば。
あわよくば、そのブロックを検知して排他制御的な処理がされてれば。
(というかそれが理想かな)

本来、ユニーク制約を貼るってのはとても簡単なこと。
ERDツールでちょっとカラム選択して制約作っておけば済む話。
実はそれだけで、運用後のメリットが享受できてるわけですね。

削除履歴テーブルなんてもの作ってられないというなら、
そこはやっぱりトレードオフ、そのリスクは背負わないといけない。
でも、リスクは背負いたくない。

「削除フラグがfalseのときだけFOOとBARがユニーク」
っていうユニーク制約が貼れたらいいのになぁって、
何人もの人から聞いた言葉ですが、まあDBMSにはないです。
かといって、トリガーでぐちゃぐちゃ頑張りたくない。

頑張ってユニーク制約

回避的なやり方をよく見かけます。

削除フラグだけじゃなく、削除日時みたいなカラムを用意して、
有効なデータには固定の "9999/12/31" とか入れて、
削除されたデータには削除した日時をそのまんま入れる。
「FOOとBARと削除日時」でユニーク制約。
アプリが有効なデータを登録するときにマジック日付を
しっかり固定で登録してくれさえすれば成立します。

回避的ってなんか後ろ向きな感じですが、
そして「うへぇ」って叫んじゃいそうなやり方ですが、
でも、やらないよりはマシかもなぁとは思います。
登録や更新が色々なプロセスから実行されて、
構造が複雑なテーブルなら、しのご言ってられないって。

その削除フラグ、どういう…?

自分もまだ思考中のテーマです。
そもそも論理削除に関しては、取り扱いが難しいと思っています。
みんな気軽に論理削除フラグを追加するけど、

o ほんとにその削除フラグ必要?
o その削除フラグはどういうときにtrueになるの?
o 検索する側は、どういうときにその削除フラグを見るの?

って、見直して欲しい場面が多々あります。
(全テーブル削除フラグとかって、ほんと憂鬱に...)

jflute流で言えば、削除フラグにはDBコメントが絶対に必須です。
単にポツンと無言で存在している削除フラグほど、
プログラマーを惑わせるものはないと考えています。

ユニーク制約の大切さ

そして...

ユニーク制約をどう貼るか!?

DB設計の中ではマニアックな要素だと思いますが、
これが運用後のメンテナンスで発揮するんですねー。
でも、貼ってしまえばあまりに当たり前のことなので、
「ユニーク制約様のおかげです」って声に出して言わないので、
やっぱりまあ地味なことには変わりはないんでしょうけど。

とりあえず DBFlute ではユニーク制約違反を、
EntityAlreadyExistsException
というクラスでcatchできるようにしています。
画面ごとに細かく制御しないにしても、
統一的に「そのデータ既にあるよんメッセージ」を出す画面に
遷移さえしていればって感じで。

注文は削除されません

…
…

ちなみにこちらの考え方、とても共感しています。
  => データの削除は非推奨 | InfoQ

…
...

追記: その後、論理削除の記事を見つけた

(2015/3/24)

DB設計するときは、
面倒がらずにしっかり考えて、
空間をデザインしていきたいものですね。

// DELETE_FLAG を付ける前に確認したいこと | Qiita
http://qiita.com/Jxck_/items/156d0a231c6968f2a474

// 論理削除が奪うもの | 泥箱的なメモ
http://dekasasaki.tumblr.com/post/69487259373