RDBでのツリー表現⼊⾨
lagénorhynque
(defprofile lagénorhynque
:id @lagenorhynque
:reading "/laʒenɔʁɛ̃k/"
:aliases ["カマイルカ🐬 "]
:languages [Clojure Haskell English français]
:interests [programming language-learning law mathematics]
:commits ["github.com/lagenorhynque/duct.module.pedestal"
"github.com/lagenorhynque/duct.module.cambium"]
:contributes ["github.com/japan-clojurians/clojure-site-ja"])
0. 事前準備
1. 隣接リスト(adjacency list)
2. 経路列挙(path enumeration)
3. ⼊れ⼦集合(nested sets)
4. 閉包テーブル(closure table)
事前準備
サンプルコード:
ローカルDB:
tree-representations-in-rdb
docker-compose.yml
mariadb:10.5.5
ツリーの本体データ⽤テーブルdepartment
CREATE TABLE `department` (
`department_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`department_id`)
);
department の初期データ
表現したいツリー
隣接リスト(adjacency list)
「隣接リスト」モデル
ツリー管理⽤のテーブル
parent_id: 親ノード
本体データ⽤テーブルに含めても良い
CREATE TABLE `department_adjacency_list` (
`department_id` int(10) unsigned NOT NULL,
`parent_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`department_id`),
FOREIGN KEY (`department_id`)
REFERENCES `department` (`department_id`)
ON DELETE CASCADE ON UPDATE RESTRICT,
FOREIGN KEY (`parent_id`)
REFERENCES `department` (`department_id`)
ON DELETE CASCADE ON UPDATE RESTRICT
);
department_adjacency_list の初期データ
すべてのノードとその階層情報の取得
distance: ルートノードからの距離
cf.
WITH RECURSIVE department_tree AS (
SELECT d.*, dal.parent_id, 0 distance
FROM department d
JOIN department_adjacency_list dal USING (department_id)
WHERE dal.parent_id IS NULL
UNION ALL
SELECT d.*, dal.parent_id, dt.distance + 1
FROM department d
JOIN department_adjacency_list dal USING (department_id)
JOIN department_tree dt ON dal.parent_id = dt.department_id
)
SELECT *
FROM department_tree;
WITH - MariaDB Knowledge Base
RDBでのツリー表現入門
特定のノードからの⼦孫ノードの取得
distance: ノード10 からの距離
WITH RECURSIVE department_tree AS (
SELECT d.*, dal.parent_id, 0 distance
FROM department d
JOIN department_adjacency_list dal USING (department_id)
WHERE d.department_id = 10
UNION ALL
SELECT d.*, dal.parent_id, dt.distance + 1
FROM department d
JOIN department_adjacency_list dal USING (department_id)
JOIN department_tree dt ON dal.parent_id = dt.department_id
)
SELECT *
FROM department_tree;
RDBでのツリー表現入門
特定のノードの⼦ノードの取得
SELECT d.*
FROM department d
JOIN department_adjacency_list dal USING (department_id)
WHERE dal.parent_id = 10;
RDBでのツリー表現入門
特定のノードの親ノードの取得
SELECT d.*
FROM department d
JOIN department_adjacency_list dal
ON d.department_id = dal.parent_id
WHERE dal.department_id = 10;
RDBでのツリー表現入門
PROS
モデルとして単純(良くも悪くもナイーブ)
直近の親/⼦ノードに対するアクセスが容易
挿⼊操作が容易
参照整合性が保証できる
CONS
再帰CTE (recursive common table expressions)
が使えないと任意階層に対するクエリが困難
削除操作が煩雑
parent_id でNULL が現れてしまう
ルートノード管理⽤のテーブルを⽤意するこ
とで回避可能
経路列挙(path enumeration)
a.k.a. 経路実体化(materialized path)
「経路列挙」モデル
ツリー管理⽤のテーブル
path: 先祖からそのノードまでの経路(パス)
本体データ⽤テーブルに含めても良い
CREATE TABLE `department_path_enumeration` (
`department_id` int(10) unsigned NOT NULL,
`path` varchar(1000) NOT NULL,
PRIMARY KEY (`department_id`),
FOREIGN KEY (`department_id`)
REFERENCES `department` (`department_id`)
ON DELETE CASCADE ON UPDATE RESTRICT
);
department_path_enumeration の初期データ
すべてのノードとその階層情報の取得
depth: ルートノードからの距離
SELECT d.*, dpe.path,
CHAR_LENGTH(dpe.path) - CHAR_LENGTH(REPLACE(dpe.path, '/', ''))
- 1 depth
FROM department d
JOIN department_path_enumeration dpe USING (department_id);
RDBでのツリー表現入門
特定のノードからの⼦孫ノードの取得
depth: ルートノードからの距離
SELECT d.*, dpe.path,
CHAR_LENGTH(dpe.path) - CHAR_LENGTH(REPLACE(dpe.path, '/', ''))
- 1 depth
FROM department d
JOIN department_path_enumeration dpe USING (department_id)
WHERE dpe.path LIKE CONCAT((
SELECT path
FROM department_path_enumeration
WHERE department_id = 10
), '%');
RDBでのツリー表現入門
特定のノードの⼦ノードの取得
SELECT d.*
FROM department d
JOIN department_path_enumeration dpe USING (department_id)
WHERE dpe.path REGEXP CONCAT((
SELECT path
FROM department_path_enumeration
WHERE department_id = 10
), 'd+/$');
RDBでのツリー表現入門
特定のノードの親ノードの取得
SELECT d.*
FROM department d
WHERE d.department_id = (
SELECT CASE WHEN path = CONCAT(department_id, '/') THEN
NULL
ELSE
REGEXP_REPLACE(path, '^(?:d+/)*(d+)/d+/$', '1')
END
FROM department_path_enumeration
WHERE department_id = 10
);
RDBでのツリー表現入門
PROS
⼦孫/先祖ノードに対するアクセスが容易
更新系操作が容易
CONS
パス⽂字列をアプリケーションコードで管理しな
ければならない
正規化されていない(第1正規形でさえない)
参照整合性が保証できない
⼊れ⼦集合(nested sets)
cf. ⼊れ⼦区間(nested intervals)
「⼊れ⼦集合」モデル
ツリー管理⽤のテーブル
left, right: 数直線上の左右の点を表す
depth: ルートノードからの距離(必須ではない)
本体データ⽤テーブルに含めても良い
CREATE TABLE `department_nested_sets` (
`department_id` int(10) unsigned NOT NULL,
`left` int(11) NOT NULL,
`right` int(11) NOT NULL,
`depth` int(10) unsigned NOT NULL,
PRIMARY KEY (`department_id`),
FOREIGN KEY (`department_id`)
REFERENCES `department` (`department_id`)
ON DELETE CASCADE ON UPDATE RESTRICT
);
department_nested_sets の初期データ
すべてのノードとその階層情報の取得
SELECT d.*, dns.left, dns.right, dns.depth
FROM department d
JOIN department_nested_sets dns USING (department_id);
RDBでのツリー表現入門
特定のノードからの⼦孫ノードの取得
SELECT d.*, dns.left, dns.right, dns.depth
FROM department d
JOIN department_nested_sets dns USING (department_id)
JOIN department_nested_sets dns2 ON dns.left BETWEEN dns2.left
AND dns2.right
WHERE dns2.department_id = 10;
RDBでのツリー表現入門
特定のノードの⼦ノードの取得
SELECT d.*
FROM department d
JOIN department_nested_sets dns USING (department_id)
JOIN department_nested_sets dns2 ON dns.left BETWEEN dns2.left
AND dns2.right
WHERE dns2.department_id = 10
AND dns.depth = (
SELECT depth
FROM department_nested_sets
WHERE department_id = 10
) + 1;
RDBでのツリー表現入門
特定のノードの親ノードの取得
SELECT d.*
FROM department d
JOIN department_nested_sets dns USING (department_id)
JOIN department_nested_sets dns2 ON dns2.left BETWEEN dns.left
AND dns.right
WHERE dns2.department_id = 10
AND dns.depth + 1 = (
SELECT depth
FROM department_nested_sets
WHERE department_id = 10
);
RDBでのツリー表現入門
PROS
⼦孫/先祖ノードに対するアクセスが容易
削除操作が容易
CONS
左右の点の値をアプリケーションコードで管理し
なければならない
正規化されていない(第1正規形でさえない)
挿⼊操作が⾮常に煩雑でコストも⾼い
「⼊れ⼦区間」では改善される
参照整合性が保証できない
閉包テーブル(closure table)
「閉包テーブル」モデル
ツリー管理⽤のテーブル
ancestor, descendant: 先祖/⼦孫関係にある
ノードの組
path_length: ancestor からdescendant ま
での距離(必須ではない)
CREATE TABLE `department_closure_table` (
`ancestor` int(10) unsigned NOT NULL,
`descendant` int(10) unsigned NOT NULL,
`path_length` int(10) unsigned NOT NULL,
PRIMARY KEY (`ancestor`, `descendant`),
FOREIGN KEY (`ancestor`)
REFERENCES `department` (`department_id`)
ON DELETE CASCADE ON UPDATE RESTRICT,
FOREIGN KEY (`descendant`)
REFERENCES `department` (`department_id`)
ON DELETE CASCADE ON UPDATE RESTRICT
);
department_closure_table の初期データ
すべてのノードとその階層情報の取得
depth: ルートノードからの距離
MAX(dct.path_length) でも求められる
SELECT d.*, COUNT(*) - 1 depth
FROM department d
JOIN department_closure_table dct
ON d.department_id = dct.descendant
GROUP BY dct.descendant;
RDBでのツリー表現入門
特定のノードからの⼦孫ノードの取得
distance: ノード10 からの距離
depth: ルートノードからの距離
SELECT d.*, dct.path_length distance, dct2.depth
FROM department d
JOIN department_closure_table dct
ON d.department_id = dct.descendant
JOIN (
SELECT descendant, COUNT(*) - 1 depth
FROM department_closure_table
GROUP BY descendant
) dct2
ON d.department_id = dct2.descendant
WHERE dct.ancestor = 10;
RDBでのツリー表現入門
特定のノードの⼦ノードの取得
SELECT d.*
FROM department d
JOIN department_closure_table dct
ON d.department_id = dct.descendant
WHERE dct.ancestor = 10
AND dct.path_length = 1;
RDBでのツリー表現入門
特定のノードの親ノードの取得
SELECT d.*
FROM department d
JOIN department_closure_table dct
ON d.department_id = dct.ancestor
WHERE dct.descendant = 10
AND dct.path_length = 1;
RDBでのツリー表現入門
PROS
⼦孫/先祖ノードに対するアクセスが容易
更新系操作が容易
参照整合性が保証できる
CONS
他のモデルよりも保持すべきデータが多くなる
まとめ
「閉包テーブル」は総合的に優れている
「隣接リスト」は最も素朴(ナイーブ)な設計だが
デメリットもいくつかある
「経路列挙」「⼊れ⼦集合」は参照整合性が保証
できない(RDBの強みが犠牲になる)
Further Reading
2章 ナイーブツリー(素朴な⽊)
第10章 グラフに⽴ち向かう
10.4 ツリー(⽊)
第10章 階層的なデータ構造
『SQLアンチパターン』
『理論から学ぶデータベース実践⼊⾨』
『E ective SQL』
第9章 ⼀歩進んだ論理設計〜SQLで⽊構造
を扱う
『達⼈に学ぶDB設計徹底指南書』
『プログラマのためのSQLグラフ原論』
What are the options for storing hierarchical
data in a relational database? - Stack Over ow

More Related Content

PDF
Where狙いのキー、order by狙いのキー
PDF
SQLアンチパターン - ナイーブツリー
PDF
イミュータブルデータモデル(世代編)
PDF
例外設計における大罪
PDF
ドメイン駆動設計のためのオブジェクト指向入門
PDF
PostgreSQLアンチパターン
PDF
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
PDF
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
Where狙いのキー、order by狙いのキー
SQLアンチパターン - ナイーブツリー
イミュータブルデータモデル(世代編)
例外設計における大罪
ドメイン駆動設計のためのオブジェクト指向入門
PostgreSQLアンチパターン
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話

What's hot (20)

PDF
DDDのモデリングとは何なのか、 そしてどうコードに落とすのか
PPTX
Azure Artifactsを触ってみよう
PDF
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
PDF
ドメイン駆動設計 基本を理解する
PDF
ドメイン駆動設計 本格入門
PDF
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
PPTX
世界一わかりやすいClean Architecture
PDF
ドメイン駆動設計サンプルコードの徹底解説
PDF
イミュータブルデータモデル(入門編)
PDF
SQLアンチパターン - ジェイウォーク
PDF
リッチなドメインモデル 名前探し
PPTX
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
PDF
3週連続DDDその1 ドメイン駆動設計の基本を理解する
PDF
C#実装から見るDDD(ドメイン駆動設計)
PDF
マイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDay
PDF
あなたの知らないPostgreSQL監視の世界
PDF
マイクロサービス 4つの分割アプローチ
PDF
Amazon Redshift パフォーマンスチューニングテクニックと最新アップデート
PPTX
RLSを用いたマルチテナント実装 for Django
PDF
Amazon Aurora - Auroraの止まらない進化とその中身
DDDのモデリングとは何なのか、 そしてどうコードに落とすのか
Azure Artifactsを触ってみよう
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
ドメイン駆動設計 基本を理解する
ドメイン駆動設計 本格入門
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
世界一わかりやすいClean Architecture
ドメイン駆動設計サンプルコードの徹底解説
イミュータブルデータモデル(入門編)
SQLアンチパターン - ジェイウォーク
リッチなドメインモデル 名前探し
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
3週連続DDDその1 ドメイン駆動設計の基本を理解する
C#実装から見るDDD(ドメイン駆動設計)
マイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDay
あなたの知らないPostgreSQL監視の世界
マイクロサービス 4つの分割アプローチ
Amazon Redshift パフォーマンスチューニングテクニックと最新アップデート
RLSを用いたマルチテナント実装 for Django
Amazon Aurora - Auroraの止まらない進化とその中身
Ad

Similar to RDBでのツリー表現入門 (20)

PDF
RDBでのツリー表現入門2024
PDF
WDD2012_SC-004
PDF
Web技術勉強会 第25回
PPT
Scala on Hadoop
PDF
20110820 metaprogramming
PPTX
SecurityとValidationの奇妙な関係、あるいはDrupalはなぜValidationをしたがらないのか
KEY
My sql casual_in_fukuoka_vol1
PDF
ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~
KEY
PDF
Groovyで楽にSQLを実行してみよう
PDF
More Better Nested Set
PDF
ソーシャルゲームの為のデータベース設計
PDF
PostgreSQL Conference Japan 2021 B2 Citus 10
PPT
次世代DaoフレームワークDoma
PDF
第2回品川Redmine勉強会(日本語全文検索)
PDF
Intoroduction of Pandas with Python
PPT
Handlersocket 20110517
PDF
ソーシャルゲームのためのデータベース設計
PDF
Databasedesignforsocialgames 110115195940-phpapp02
PDF
メタメタプログラミングRuby
RDBでのツリー表現入門2024
WDD2012_SC-004
Web技術勉強会 第25回
Scala on Hadoop
20110820 metaprogramming
SecurityとValidationの奇妙な関係、あるいはDrupalはなぜValidationをしたがらないのか
My sql casual_in_fukuoka_vol1
ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~
Groovyで楽にSQLを実行してみよう
More Better Nested Set
ソーシャルゲームの為のデータベース設計
PostgreSQL Conference Japan 2021 B2 Citus 10
次世代DaoフレームワークDoma
第2回品川Redmine勉強会(日本語全文検索)
Intoroduction of Pandas with Python
Handlersocket 20110517
ソーシャルゲームのためのデータベース設計
Databasedesignforsocialgames 110115195940-phpapp02
メタメタプログラミングRuby
Ad

More from Kent Ohashi (20)

PDF
関数型言語テイスティング: Haskell, Scala, Clojure, Elixirを比べて味わう関数型プログラミングの旨さ
PDF
純LISPから考える関数型言語のプリミティブ: Clojure, Elixir, Haskell, Scala
PDF
From Scala/Clojure to Kotlin
PDF
TDD with RDD: Clojure/LispのREPLで変わる開発体験
PDF
🐬の推し本紹介2024: 『脱・日本語なまり 英語(+α)実践音声学』
PDF
do Notation Equivalents in JVM languages: Scala, Kotlin, Clojure
PDF
map関数の内部実装から探るJVM言語のコレクション: Scala, Kotlin, Clojureコレクションの基本的な設計を理解しよう
PDF
Kotlin Meets Data-Oriented Programming: Kotlinで実践する「データ指向プログラミング」
PDF
ミュータビリティとイミュータビリティの狭間: 関数型言語使いから見たKotlinコレクション
PDF
インターフェース定義言語から学ぶモダンなWeb API方式: REST, GraphQL, gRPC
PDF
Team Geek Revisited
PDF
Scala vs Clojure?: The Rise and Fall of Functional Languages in Opt Technologies
PDF
Clojureコレクションで探るimmutableでpersistentな世界
PDF
英語学習者のためのフランス語文法入門: フランス語完全理解(?)
PDF
JavaからScala、そしてClojureへ: 実務で活きる関数型プログラミング
PDF
実用のための語源学入門
PDF
メタプログラミング入門
PDF
労働法の世界
PDF
Clojureで作る"simple"なDSL
PDF
GraphQL入門
関数型言語テイスティング: Haskell, Scala, Clojure, Elixirを比べて味わう関数型プログラミングの旨さ
純LISPから考える関数型言語のプリミティブ: Clojure, Elixir, Haskell, Scala
From Scala/Clojure to Kotlin
TDD with RDD: Clojure/LispのREPLで変わる開発体験
🐬の推し本紹介2024: 『脱・日本語なまり 英語(+α)実践音声学』
do Notation Equivalents in JVM languages: Scala, Kotlin, Clojure
map関数の内部実装から探るJVM言語のコレクション: Scala, Kotlin, Clojureコレクションの基本的な設計を理解しよう
Kotlin Meets Data-Oriented Programming: Kotlinで実践する「データ指向プログラミング」
ミュータビリティとイミュータビリティの狭間: 関数型言語使いから見たKotlinコレクション
インターフェース定義言語から学ぶモダンなWeb API方式: REST, GraphQL, gRPC
Team Geek Revisited
Scala vs Clojure?: The Rise and Fall of Functional Languages in Opt Technologies
Clojureコレクションで探るimmutableでpersistentな世界
英語学習者のためのフランス語文法入門: フランス語完全理解(?)
JavaからScala、そしてClojureへ: 実務で活きる関数型プログラミング
実用のための語源学入門
メタプログラミング入門
労働法の世界
Clojureで作る"simple"なDSL
GraphQL入門

RDBでのツリー表現入門