読者です 読者をやめる 読者になる 読者になる

ネットの海の片隅で

技術ネタの放流、あるいは不法投棄。

論理削除の必要性をひねり出そうとして失敗した

f:id:s_osa:20140625205802p:plain

はじめに

このエントリは僕の平凡な考えを淡々と書いたものです。過度な期待はしないでください。 あと、部屋は明るくしてディスプレイから30センチは離れて見やがってください。

物理削除したくない!

  • 外部キー制約を張っていない場合
    • 削除したレコードを参照しているレコードがエラーを吐く
  • 外部キー制約を張っている場合
    • 参照されているレコードは削除できない
    • カスケードで削除すると影響範囲が大きい
  • レコードがあったという事実は記録しておきたい
    • 事実は消えない

論理削除もしたくない!

  • 削除するときにdeletedフラグなどを立てる
    • あらゆるクエリにWHERE deteled = falseとか入ってきて汚い
  • 物事は「削除」されるのではない
    • 注文は削除されるのではなくキャンセルされる
    • 従業員は削除されるのではなく解雇される

じゃあ、どうするの?

大雑把に書いてきましたが、このあたりのことが

あたりで触れられています。

そこでは、

IsDeletedフラグを使う代わりに、Dahan氏はデータの状態を表すフィールドを保持することを提案している。例えば、有効、中止、キャンセル、廃止予定のような状態だ。

といった削除フラグの代わりに各種のステータスを適切に付与することが提案されています。

でも「削除」って必要では?

ここまで「削除」を排除する方向で話を進めてきた結果、正常系に関しては論理削除の代わりにステータスの管理によってレコードの状態を表現することで論理削除を行わないという案を得ました。

しかし、「削除」が欲しくなるシーンはやはり存在します。

たとえば、若干極論ですが、

猫がキーボードを叩いて、フォームに入力・送信してしまった

というケースでは、「猫による入力」なんていうステータスを作るのはナンセンスなので、そのデータを「削除」したいと考えてしまいます。

それは本当に「削除」なのか?

ここで、猫による入力で作成された不要なレコードを「削除」したいと考えましたが、「今、猫によって作成されたレコードとは何か」ということをよくよく考えてみると、それは削除(済み|されるべき)レコードではなく誤入力レコードです。

すると、本当に行いたいのは「削除」ではなく、「そのデータは間違っていて(基本的には)不要なデータ」ということを示すことのハズです。

そして、誤入力は猫に限った話ではありません。人間は必ずミスをするので、正常系とまでは言えないまでも開発を行う上で想定すべき範囲内だと思います。

たとえば、

  • 「うわっ、入力ミスった」
  • 「どのレコード?」
  • 「これです、これ」
  • 「あーなるほどね。『削除済み』にしておいて」

という会話はそれなりに存在しそうですが、「ミス」→「削除する」というのは実は不自然で「ミス」→「ミスだと記録する」というのが自然な流れだと思います。*1*2

そもそも論理削除って

「deletedフラグ」だろうが「削除済みステータス」だろうが、どっちにしてもDBに入ってるんだから削除されてねーじゃん!*3

物理削除に関しては「ユーザの退会→個人情報削除」とかの「本当に削除したいケース」が思いつきますが、論理削除で本当に「削除」を記録したいケースは思いつきませんでした*4

現時点での個人的考え

  • 事実の削除である物理削除を認めない
  • レコードのステータスを管理する
  • 「削除」だと思っても削除じゃなかったりするからよく考える
    • 言葉を吟味する
    • 日々、「受注も発注も両方『注文』っていうのやめて><」とか思ってたけど、自分も結構できてなかった
  • とはいえ、実装上妥協する場合もある

おまけ:ステータスによる絞り込みの煩雑さ

ステータスによるレコードのステータス管理を導入しても論理削除をしたくない理由で挙げた

  • 削除するときにdeletedフラグなどを立てる
    • クエリにWHERE deteled = falseとか入ってきて汚い

は一切解決しません。

WHERE deteled = falseWHERE status = 'available'とかになるだけです。

じゃあ、これをどう解決するかというと、普通にViewを使うのが一番良いんじゃないかと思います。

商品が登録されているitemsテーブルがあるとしたら、WHERE status = 'available'で絞ってavailable_itemsビューを作る、みたいな。

Railsの場合、VIiewだとActiveRecord::Base#saveあたりが動かないのが辛いところですが、PostgreSQL9.3からはUpdatable Viewが使えるので、そのあたりと組み合わせていけば幸せになれる気がします。

SQLアンチパターン

*1:誤入力データと後々追加したデータの一意性制約がぶつかったりするとまた面倒なんですが、まだそこまでは考えきれていません。

*2:コメントにて教えていただいたところによると、部分インデックスで解決できます。そう、PostgreSQLならね!

*3:そういう意味では物理削除のほうが実際に削除されているという点で事実に即している気がします。

*4:実は、前述の猫入力も「論理削除が欲しいケース」としてひねり出したのですが、考えを詰めてみると論理削除が不要になりました。