【SQL】 SQL アンチパターン ~ Naive Trees(素朴な木) ~

■ はじめに

Wise men learn by other men's mistakes;
fools by their own

https://dk521123.hatenablog.com/entry/2016/07/02/212547

の続き。
今回は、Naive Trees(素朴な木) について学ぶ。

 ■ 問題点

 * 親・子関係があるデータ構造(例「会社の組織図」)をテーブルに落とし込む際に、
   一つの解決方法として、「自分の親IDを用意する(隣接リスト(Adjacency List))」
   っといった方法があるが、これじゃダメって話。

 ■ 解決案

[1] 経路列挙(Path Enumeration, Materialized Path)

 => 親階層を表す文字列(大元の親ID/.../直近の親ID)を属性に格納

[2] 入れ子集合(Nested Set)

 => 子孫の集合に関する情報を各行に格納

[3] 閉包テーブル(Closure Table)

 => 組織関係を別テーブルに設ける
 => そのテーブルには、直接の親子関係だけではなく、階層全体の親子関係を格納する

 ■ サンプル

 ダメな例

-- テーブル
CREATE TABLE department
(
id char(4) NOT NULL PRIMARY KEY,
name varchar(20) NOT NULL,
parent_id char(4) NULL,
FOREIGN KEY (parent_id) REFERENCES department(id)
) ENGINE=InnoDB;

-- データ
INSERT INTO department(id, name, parent_id) VALUES('0000', 'Overseas division', NULL);
INSERT INTO department(id, name, parent_id) VALUES('1000', 'IT', '0000');
INSERT INTO department(id, name, parent_id) VALUES('1001', 'IT 1st', '1000');
INSERT INTO department(id, name, parent_id) VALUES('1002', 'IT 2nd', '1000');
INSERT INTO department(id, name, parent_id) VALUES('1003', 'IT 3rd', '1000');
INSERT INTO department(id, name, parent_id) VALUES('2000', 'Sales', '0000');
INSERT INTO department(id, name, parent_id) VALUES('2001', 'Sales 1st', '2000');
INSERT INTO department(id, name, parent_id) VALUES('2002', 'Sales 2nd', '2000');
INSERT INTO department(id, name, parent_id) VALUES('3000', 'Planning', '0000');
INSERT INTO department(id, name, parent_id) VALUES('3001', 'Planning 1st', '3000');

 [1] 経路列挙

DROP TABLE department
-- テーブル
CREATE TABLE department
(
id char(4) NOT NULL PRIMARY KEY,
name varchar(20) NOT NULL,
path varchar(100) NULL
) ENGINE=InnoDB;

-- データ
INSERT INTO department(id, name, path) VALUES('0000', 'Overseas division', NULL);
INSERT INTO department(id, name, path) VALUES('1000', 'IT', '0000');
INSERT INTO department(id, name, path) VALUES('1001', 'IT 1st', '0000/1000');
INSERT INTO department(id, name, path) VALUES('1002', 'IT 2nd', '0000/1000');
INSERT INTO department(id, name, path) VALUES('1003', 'IT 3rd', '0000/1000');
INSERT INTO department(id, name, path) VALUES('2000', 'Sales', '0000');
INSERT INTO department(id, name, path) VALUES('2001', 'Sales 1st', '0000/2000');
INSERT INTO department(id, name, path) VALUES('2002', 'Sales 2nd', '0000/2000');
INSERT INTO department(id, name, path) VALUES('3000', 'Planning', '0000');
INSERT INTO department(id, name, path) VALUES('3001', 'Planning 1st', '0000/3000');

 [3] 閉包テーブル(Closure Table)

DROP TABLE department;

-- テーブル
CREATE TABLE department
(
id char(4) NOT NULL PRIMARY KEY,
name varchar(20) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE hierarchy
(
parent_id char(4) NOT NULL,
child_id char(4) NOT NULL,
FOREIGN KEY (parent_id) REFERENCES department(id),
FOREIGN KEY (child_id) REFERENCES department(id)
) ENGINE=InnoDB;
-- データ
INSERT INTO department(id, name) VALUES('0000', 'Overseas division');
INSERT INTO department(id, name) VALUES('1000', 'IT');
INSERT INTO department(id, name) VALUES('1001', 'IT 1st');
INSERT INTO department(id, name) VALUES('1002', 'IT 2nd');
INSERT INTO department(id, name) VALUES('1003', 'IT 3rd');
INSERT INTO department(id, name) VALUES('2000', 'Sales');
INSERT INTO department(id, name) VALUES('2001', 'Sales 1st');
INSERT INTO department(id, name) VALUES('2002', 'Sales 2nd');
INSERT INTO department(id, name) VALUES('3000', 'Planning');
INSERT INTO department(id, name) VALUES('3001', 'Planning 1st');

INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '1000');
INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '2000');
INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '3000');
INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '1001');
INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '1002');
INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '1003');
INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '2001');
INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '2002');
INSERT INTO hierarchy(parent_id, child_id) VALUES('0000', '3001');
INSERT INTO hierarchy(parent_id, child_id) VALUES('1000', '1001');
INSERT INTO hierarchy(parent_id, child_id) VALUES('1000', '1002');
INSERT INTO hierarchy(parent_id, child_id) VALUES('1000', '1003');
INSERT INTO hierarchy(parent_id, child_id) VALUES('2000', '2001');
INSERT INTO hierarchy(parent_id, child_id) VALUES('2000', '2002');
INSERT INTO hierarchy(parent_id, child_id) VALUES('3000', '3001');

 参考文献

http://qiita.com/hirashunshun/items/06adf4f42f03a9f3b63d
http://d.hatena.ne.jp/asakichy/20160623/1466632754
http://toxn.hatenablog.com/entry/2015/02/20/003202
入れ子集合
http://gihyo.jp/dev/serial/01/sql_academy2/000501

 関連記事

 SQL アンチパターン ~ 目次 ~
https://dk521123.hatenablog.com/entry/2016/07/02/212547