NGW開発BLOG
← 記事一覧へ
·#023

「今の所属」で過去を絞ってはいけない申請日時点の所属で判定する

決裁状況を所属で絞るとき、現在の所属で判定すると、異動者の過去の申請が異動先の部署に現れてしまう——見えてはいけない申請が見えてしまう。正しくは申請日時点の所属で判定する話。

見えてはいけない申請が、見えてしまう

決裁状況の一覧に「所属」で絞り込むフィルタがある。ある部署を選ぶと、その部署に関わる申請だけが並ぶ。素直な機能だ。だがある日、こんなことが起きた。

Aさんは3月まで総務課にいた。4月の人事異動で企画課へ移る。そして5月、企画課の職員が何気なく決裁状況で過年度分を検索すると、そこに総務課時代のAさんの申請が並んでいた。異動先の企画課からは、本来なら見えてはいけない申請である。

原因を追うと、フィルタの照合先が職員マスタの「現在の所属」だった。職員マスタは常に最新の状態を指す。Aさんが4月に企画課へ異動すれば、所属カラムは企画課に書き換わる。すると総務課時代である3月の申請までが企画課の持ち物として扱われ、Aさんがまだ在籍していなかった企画課の一覧に現れてしまう。見えてはいけない相手に、過去の申請が見えてしまうのだ。逆に、実際に申請を出した総務課の視点からは、その申請が丸ごと消える。

つまりこれは、現在のマスタで過去のイベントを解釈してしまったバグである。所属という属性は時間とともに変わるのに、変わった後の値で過去を裁いていた。

イベントには、発生時点の属性を紐付ける

正しくは「申請日時点の所属」で判定する。その申請が行われたまさにその時、職員がどこに所属していたかで絞る。

実現方法は二つある。ひとつは、申請というイベントを記録する瞬間に、その時点の所属をレコード側へ書き写しておくこと。もうひとつは、異動履歴(所属の履歴テーブル)から申請日を基準に as-of で引くことだ。

-- ダメな例: 現在の所属で過去を絞る
SELECT * FROM applications a
JOIN staff s ON s.staff_cd = a.staff_cd
WHERE s.division_cd = ?;      -- s は「今」の所属

-- 良い例: 履歴から申請日時点の所属を引いて絞る
SELECT * FROM applications a
JOIN staff_history h
  ON h.staff_cd = a.staff_cd
 AND a.applied_on BETWEEN h.valid_from AND h.valid_to
WHERE h.division_cd = ?;      -- h は「あの時」の所属

前者はイベント側に属性を非正規化して持つので照合が速く、履歴の整備状況に依存しない。後者はマスタ側を単一の真実として保てるが、履歴に有効期間(valid_from / valid_to)が正しく積まれていることが前提になる。どちらを選ぶにせよ、原則は変わらない。イベントを、そのイベントが起きた時点の世界で解釈することだ。

決裁状況のフィルタは前者に寄せて直した。ちなみにこの一覧は同じ時期に性能改善(複合索引の追加)も入れているが、それは別稿に譲る。正しさと速さは別の問題で、片方を直しても片方は直らない。

マスタは「今」、業務記録は「あの時」

この話は所属に限らない。役職、単価、税率、住所——「時点を持つデータ(temporal data)」はどれも同じ罠を抱えている。会計で当時の単価を今の単価で再計算したら伝票は狂うし、人事で当時の役職を今の役職で読み替えたら履歴が壊れる。「当時どうだったか」が問われるドメインでは、これは必須の考え方だ。

関連して、役職から個人を解決するときに基準日を使う設計も同じ根から来ている。「この役職の人は誰か」は時期によって変わるので、基準日を与えて履歴を引く。時点を明示しない解決は、いつのまにか「今」で過去を上書きしてしまう。

教訓はひとつに畳める。マスタは「今」を表す。だが業務記録はしばしば「あの時」を問う。 現在値で過去を解釈しないよう、イベントには発生時点の属性を保持するか、時点を指定して履歴を引く。異動・改称・改定のある世界では、この一線が正しさの分かれ目になる。