【SQL】部署マスタの設計で「自己参照」を採用してみた
今回は、DB設計における「階層構造の扱い方」についてです。
先日、社内システムで「部署マスタ」を設計する機会がありました。そこで「自己参照(Self-Referencing)」という設計パターンを採用したところ、非常にスッキリと管理できたので、その知見を共有します。
「組織図やカテゴリ分けのDB設計、どうするのが正解?」と悩んでいる方の参考になれば嬉しいです。
🧐 背景:なぜ「自己参照」を選んだのか?
今回作成するのは「部署マスタ」です。
組織図というのは、「本部 → 事業部 → 課 → 係」のように階層構造になっています。
最初は以下のような「階層ごとにテーブルを分ける設計」も頭をよぎりました。
-
t_divisions(本部テーブル) -
t_departments(部テーブル) -
t_sections(課テーブル)
しかし、これだと「組織改編で『室』が増えたらどうする?」「テーブル結合(JOIN)が面倒すぎない?」という問題が発生します。
今回の要件を整理すると、部署のデータ数自体はそこまで多くないことがわかっていました。
そこで、「1つのテーブル内で完結させた方が圧倒的に管理しやすい」と判断し、自己参照を採用しました。
💡 「自己参照(Self-Referencing)」とは?
一言で言うと、「親と子の関係を、1つのテーブルの中で表現する仕組み」のことです。
技術的には、テーブル内のカラムが、同じテーブルの主キー(PK)を参照する外部キー(FK)として設定されている状態を指します。
実際のテーブル設計
イメージしやすいように、実際のテーブル構造(簡略版)を見てみましょう。
テーブル名:t_departments
| カラム名 | データ型 | 説明 |
|---|---|---|
department_id |
INT | 主キー (PK) |
department_name |
VARCHAR | 部署名 |
parent_department_id |
INT | 親部署ID (自己参照FK) |
ポイントは parent_department_id です。ここには、親となる部署の department_id が入ります。一番上の階層(親がいない場合)は NULL にします。
データの入り方(レコード例)
| department_id | department_name | parent_department_id | 備考 |
|---|---|---|---|
| 100 | 営業本部 | NULL |
ここがトップ |
| 101 | 第一事業部 | 100 | 親は営業本部 |
| 102 | 一課 | 101 | 親は第一事業部 |
これだけで、「営業本部 > 第一事業部 > 一課」というツリー構造が表現できています。
🚀 自己参照を採用するメリット
実際に実装してみて感じたメリットは以下の2点です。
1. 階層の深さが無制限になる
これが最大の強みです。「本部→部→課」の下に、急に「係」や「班」ができても、テーブル構造を変更する必要がありません。単にデータを追加(INSERT)するだけで対応可能です。
仕様変更に強い設計と言えます。
2. テーブルが1つで済む
「本部テーブル」「課テーブル」のように管理対象が分散しません。マスタ管理画面を作る際も、1つのテーブルに対するCRUDを作るだけで済むため、実装工数が削減できます。
⚠️ 知っておくべきデメリット・注意点
採用する際は以下の点に注意が必要です。
1. 階層構造を取得するクエリが少し難しい
「ある部署の配下すべてを取得したい」といった場合、単純な SELECT や JOIN では深さがわからず苦労します。
これに対応するには、再帰クエリ(WITH RECURSIVE) という少し高度なSQLを使うのが一般的です。
-- 例: 営業本部(ID:100)配下のすべての組織を取得する再帰クエリ
WITH RECURSIVE DepartmentTree AS (
SELECT department_id, department_name, parent_department_id
FROM t_departments
WHERE department_id = 100 -- 起点
UNION ALL
SELECT d.department_id, d.department_name, d.parent_department_id
FROM t_departments d
INNER JOIN DepartmentTree dt ON d.parent_department_id = dt.department_id
)
SELECT * FROM DepartmentTree;
2. 無限ループのリスクがある
「Aの親はB、Bの親はA」というデータが入ってしまうと、論理的に循環(ループ)してしまい、プログラムが無限ループに陥る危険性があります。
データの登録・更新時に、アプリ側で「自分の子孫を親に設定していないか」などのチェック処理を入れるのが鉄則です。
📚 その他のユースケース
この「自己参照」パターンは、部署マスタ以外にも以下のような「ツリー構造(木構造)」を持つデータに最適です。
- ECサイトのカテゴリ(家電 > テレビ > 4Kテレビ)
- 掲示板のコメント(親コメントへの返信、そのまた返信)
- ファイルシステム(フォルダの中にフォルダ)
- 社員管理(誰が誰の上司か)
🎁 まとめ
- データ量が膨大でなく、階層構造を持つデータなら「自己参照」は非常に有力な選択肢。
- テーブル構成をシンプルに保てるが、再帰クエリの知識は必要になる。
- 無限ループにならないよう、アプリ側でのバリデーションは必須。
DB設計は「適材適所」が全てです。要件に合わせて、ベストな形を選んでいきましょう!