SlideShare a Scribd company logo
SQLアンチパターンその2

素

朴

な

木

ナイーブツリー
ナイーブツリー
is
直近の親のみを参照する事でツリー構造を表現する
SQLアンチパターン
シナリオ
A「ブログのコメント欄さ」
 B「はい」
 A「よく議論やってるじゃん?」
 B「ですね」
 A「スレッド形式で表示できたら分かりやす
いと思うんよね」
 B「考えてみます」

シナリオ


id

B「今のテーブル定義こんな感じだから」
comment

1

たけのこ派駆逐したい

2

きのこ派怖いわー

3

>>1 同意

4

>>2 たけのこ汚い

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い
シナリオ


id

B「コメント間に親子関係があればいいわけ
で」
comment

1

たけのこ派駆逐したい

2

きのこ派怖いわー

3

>>1 同意

4

>>2 たけのこ汚い

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い
シナリオ


B「こうすればいいか」

id parent

comment

1

null

たけのこ派駆逐したい

2

1

きのこ派怖いわー

3

1

>>1 同意

4

2

>>2 たけのこ汚い

たけのこ派駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意
シナリオ


B「こうすればいいか」

id parent

comment

1

null

たけのこ派駆逐したい

2

1

きのこ派怖いわー

3

1

>>1 同意

4

2

>>2 たけのこ汚い

たけのこ派駆逐したい

素

朴

な

木

ナイーブツリー

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意
Bの主張


葉の追加も簡単だし参照整合性も維持でき
るし最強に見える。
Bの主張


葉の追加も簡単だし参照整合性も維持でき
るし最強に見える。
 ツリーの参照は?

 コメント数の数えるとか集約系の関数は?
 非葉ノードの削除は?サブツリーの昇格は?
ナイーブツリー選択のデメリット


(サブ)ツリーを引っこ抜けない
 ツリー全体
 あるいはあるコメントのサブツリー
ナイーブツリー選択のデメリット


(サブ)ツリーを引っこ抜けない
 ツリー全体
 あるいはあるコメントのサブツリー

select
c1.*
from comm c1

たけのこ派駆逐したい
ナイーブツリー選択のデメリット


(サブ)ツリーを引っこ抜けない
 ツリー全体
 あるいはあるコメントのサブツリー

select
c1.*, c2.*
from comm c1
left outer join comm c2
on c1.id = c2.parent

たけのこ派駆逐したい

きのこ派怖いわー
コメント1を親に持つコメント
ナイーブツリー選択のデメリット


(サブ)ツリーを引っこ抜けない
 ツリー全体
 あるいはあるコメントのサブツリー

select
c1.*, c2.*, c3.*
from comm c1
left outer join comm c2
on c1.id = c2.parent
left outer join comm c3
on c2.id = c3.parent

たけのこ派駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い
コメント1を親に持つコメント
を親に持つコメント
ナイーブツリー選択のデメリット


(サブ)ツリーを引っこ抜けない
 ツリー全体
 あるいはあるコメントのサブツリー



n階層取得したい場合は…?
 子が存在し続ける限り自動でjoinし続けてくれ

るSQLは書けない
 取得できる階層が固定される

 必然的にある任意の(サブ)ツリーのコメント総

数みたいな集約関数も使えない
ナイーブツリー選択のデメリット


非葉ノードとそのサブツリーの削除
 コメント1削除
 コメント1を親に持つコメントを削除

 …とやりたいけど参照整合性のため子から削除

する必要がある
 c1、c1を親に持つ(c2)、c2を親に持つ…を結果
が返らなくなるまで繰り返して削除対象のコメ
ントを取得し、子側からdeleteしないとダメ
 とは言えdelete on cascadeが使えれば、これ
は自動化出来る
ナイーブツリー選択のデメリット


非葉ノードとそのサブツリーの削除
たけのこ派駆逐したい

select
ナイーブツリー選択のデメリット


非葉ノードとそのサブツリーの削除
たけのこ派駆逐したい

きのこ派怖いわー

select

select
ナイーブツリー選択のデメリット


非葉ノードとそのサブツリーの削除
たけのこ派駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

select

select
select
ナイーブツリー選択のデメリット


非葉ノードとそのサブツリーの削除
たけのこ派駆逐したい

きのこ派怖いわー

select

select
delete
ナイーブツリー選択のデメリット


非葉ノードとそのサブツリーの削除
たけのこ派駆逐したい

select

delete
ナイーブツリー選択のデメリット


非葉ノードとそのサブツリーの削除
delete
ナイーブツリー選択のデメリット


非葉ノード削除とそのサブツリーの昇格
 非葉ノードにくっついていたサブツリーを自分

の親の子へ昇格させる場合
 こちらはdelete on cascadeズパーンとはいか
ない
 削除前に自分の子を取得し、子の親を自分の親
へ付け替えた上で削除する必要がある
ナイーブツリー選択のデメリット


非葉ノード削除とそのサブツリーの昇格
たけのこ派駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意

←削除したい
ナイーブツリー選択のデメリット


非葉ノード削除とそのサブツリーの昇格
たけのこ派駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意

←削除したい
←子の親を
ナイーブツリー選択のデメリット


非葉ノード削除とそのサブツリーの昇格
たけのこ派駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意

←削除したい
←自分の親へ更新
ナイーブツリー選択のデメリット


非葉ノード削除とそのサブツリーの昇格
たけのこ派駆逐したい

←削除
>>2 たけのこ汚い

>>1 同意
ナイーブツリー選択のデメリット


非葉ノード削除とそのサブツリーの昇格
たけのこ派駆逐したい

>>2 たけのこ汚い

>>1 同意
ナイーブツリー選択のデメリット


まとめ
 再帰的にクエリ吐けるとかじゃなきゃやってら

れない
 サブツリー一括の削除や移動はまだしもノード
の昇格とかちょっと凝った操作を実現するのは
つらい。
B「僕は…どうしたらよかったんだ…」
解決策


代替ツリーモデルを使いましょう
 ツリーを表現する他の方法
 経路列挙モデル
 入れ子集合モデル
 閉包テーブルモデル
解決策 : 経路列挙モデル
id path

comment

1

1/

たけのこ派駆逐したい

2

1/2

きのこ派怖いわー

3

1/3

>>1 同意

4

1/2/4

>>2 たけのこ汚い
たけのこ駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意
解決策 : 経路列挙モデル


メリット


サブツリーの一括取得ができる
 likeを用いたパターンマッチ
 path

like ‘1/2/%’

ノードの追加は親ノードのpathにidを連結した
path文字列で追加する
 葉ノードの削除はdeleteするだけ




デメリット
>突然のジェイウォーク!<
 長さ制限とか参照整合性とか同様の脆弱性
 非葉ノードの削除、サブツリーの昇格とか移動とか
は子のpathを全て更新しないといけないので隣接
ツリーよりだるい

解決策 : 経路列挙モデル
id path

comment

1

1/

たけのこ派駆逐したい

2

1/2

きのこ派怖いわー

3

1/3

>>1 同意

4

1/2/4

2に子を追加

>>2 たけのこ汚い
たけのこ駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意
解決策 : 経路列挙モデル
id path

comment

1

1/

たけのこ派駆逐したい

2

1/2

きのこ派怖いわー

3

1/3

>>1 同意

4

1/2/4

>>2 たけのこ汚い

5

1/2/5

きのこfudやめろ
たけのこ駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意

きのこfudやめろ
解決策 : 経路列挙モデル
id path

comment

1

1/

たけのこ派駆逐したい

2

1/2

きのこ派怖いわー

3

1/3

>>1 同意

4

1/2/4

>>2 たけのこ汚い

5

1/2/5

path like ‘1/2%’で
サブツリーを選択できる。

きのこfudやめろ
たけのこ駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意

きのこfudやめろ
解決策 : 入れ子集合モデル
id

left

right

comment

1

1

8

たけのこ派駆逐したい

2

2

5

きのこ派怖いわー

3

6

7

>>1 同意

4

3

4

>>2 たけのこ汚い
たけのこ駆逐したい
1

8

きのこ派怖いわー
2

5

>>2 たけのこ汚い

3

>>1 同意

4

6

7
解決策 : 経路列挙モデル


Left? Right?






メリット





Left : その子ノードの持つどのLR値よりも小さい値
Right: その子ノードの持つどのLR値よりも大きな値
深さ優先探索で求まる
子孫、先祖ノードの一括取得ができる
ノードを削除すると自動的に子ノードが親ノードの子へ
昇格する

デメリット


ノードの追加や更新(移動)が複雑過ぎる




LR値の再計算が必要

隣接リストでは簡単な直近の子の取得がだるい
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

きのこ派怖いわー
2

>>2 たけのこ汚い

>>1 同意
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

きのこ派怖いわー
2

>>2 たけのこ汚い
3

>>1 同意
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

きのこ派怖いわー
2

>>2 たけのこ汚い
3

4

>>1 同意
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

6
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

6

7
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

8

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

6

7
解決策 : 入れ子集合モデル
サブツリーの
どのLR値より小さい

たけのこ駆逐したい
1

8

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

6

7
解決策 : 入れ子集合モデル
サブツリーの
どのLR値より大きい

たけのこ駆逐したい
1

8

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

6

7
解決策 : 入れ子集合モデル
1の子孫ツリーの取得
→L値が1~8のコメント

たけのこ駆逐したい
1

8

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

6

7
解決策 : 入れ子集合モデル
1の子孫ツリーの取得
→L値が1~8のコメント

たけのこ駆逐したい
1

8

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

6

7
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

8

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

4の先祖ツリーの取得
→L <= 3 <= R を満たすコメント

6

7
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

8

きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

4の先祖ツリーの取得
→L <= 3 <= R を満たすコメント

6

7
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

8

2にノードを追加
きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意

4

6

7
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

8

2にノードを追加
きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意
6

7

きのこfudやめろ
4

5

6

↑
L:R = 5:6を追加したい
さっきの深さ優先探索より
2:5の子に追加するのでこのノードのLR値は5:6になるはず
(親のノードのR値を追加しようとするノードのL値にする)
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

8

2にノードを追加
きのこ派怖いわー
2

5

>>2 たけのこ汚い
3

>>1 同意
6

7

きのこfudやめろ
4

5

6

↑
追加されたノード以降のLR値を再計算(+2する)
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

10

2にノードを追加
きのこ派怖いわー
2

7

>>2 たけのこ汚い
3

>>1 同意
8

9

きのこfudやめろ
4

5

6
解決策 : 入れ子集合モデル
たけのこ駆逐したい
1

10

2にノードを追加
きのこ派怖いわー
2

7

>>2 たけのこ汚い
3

>>1 同意
8

9

きのこfudやめろ
4

5

6
↑
追加
解決策 : 閉包テーブルモデル
id

comment

先祖

子孫

2

2

1

たけのこ派駆逐したい

1

1

2

4

2

きのこ派怖いわー

1

2

3

3

3

>>1 同意

1

4

4

4

4

>>2 たけのこ汚い

1

3
たけのこ駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

>>1 同意
解決策 : 閉包テーブルモデル


ノード同士の関係性を定義するテーブルを追加
する


直近の親子関係だけじゃなく、全体のパスを持つ
 離れたノードの親子関係も
 自分自身を参照するパスも含める



メリット

ツリーの取得、移動、ツリーの削除、ツリーの昇格、
マルチルート、複数の親への所属などとにかく柔軟
 入れ子集合ほどは複雑じゃない




デメリット
柔軟過ぎる
 入れ子ほどじゃないと言うだけでそこそこ複雑
 パステーブルのデータ量が激ふえする

解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A
B
B

C
D
D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

B
B

C
D
D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

A

B

C
D
D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A

A

B

C

D
D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A

B

C

D

A

D
D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A

D

B

B

B

C

D

A

D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A

D

B

B

B

D

B

C

D

A

D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A

D

B

B

B

D

C

C

B

C

D

A

D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A

D

B

B

B

D

C

C

D

D

B

C

D

A

D

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A

B

B

D

C

C

D

D

C

D

B

B

C

D

A

D

直近だけでなく全てのノードの親子関係を定義
パステーブルの1行は上図の矢印1本に相当する
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A

D

B

B

B

D

C

C

D

D

B

C

D

A

D

Aの子孫ツリー
→Aを先祖として持つ子孫

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A

D

B

B

B

D

C

C

D

D

B

C

D

A

D

Dの先祖ツリー
→Dを子孫として持つ先祖

C
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

B

A

B

C

A
A
B

B

B

D

C

C

D

D

C

D

E

B

C

D

A

D

E
E

E

A

E

Bの子としてEを追加
B
E
→自己参照追加
→Bを子孫として持つ先祖の子孫にEを追加
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

B

A

C

C

A

D

D

A

E

B

B

B

D

B

E

C

C

D

D

E

E

B

E

A

B

D

C

E

Bの子としてEを追加
→自己参照追加
→Bを子孫として持つ先祖の子孫にEを追加
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

B

A

C

C

A

D

D

A

E

B

B

B

D

B

E

C

C

D

D

E

E

B

E

A

B

D

C

E

Bのツリーをまるごと削除
→Bを子孫とする関係を削除
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

B

A

C

C

A

D

D

A

E

B

B

B

D

B

E

C

C

D

D

E

E

B

E

A

B

D

C

E

Bのツリーをまるごと削除
→Bを子孫とする関係を削除
→Bの子孫を子孫とする関係を削除
(Bを先祖とするノードを子孫として持つ関係)
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

C

C

C

B

A

B

C

C

D
E

D

E

Bのツリーをまるごと削除
→Bを子孫とする関係を削除
→Bの子孫を子孫とする関係を削除
(Bを先祖とするノードを子孫として持つ関係)
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

B

A

C

C

A

D

D

A

E

B

B

B

D

B

E

C

C

D

D

E

E

B

E

A

B

D

C

E

Bを削除
→Bを子孫とする関係を削除
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

B

A

C

C

A

D

D

A

E

B

B

B

D

B

E

C

C

D

D

E

E

B

E

A

B

D

C

E

Bを削除
→Bを子孫とする関係を削除
→Bを先祖とする関係を削除
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

C

A

D

C

A

E

D

C

C

D

D

E

E

B

E

A

B

D

C

E

Bを削除 → C, Eが自動的にAの子に昇格
→Bを子孫とする関係を削除
→Bを先祖とする関係を削除
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

B

A

C

C

A

D

D

A

E

B

B

B

D

B

E

C

C

D

D

E

E

B

E

A

B

D

C

E

BをCの子へ移動
→Bを子孫とする関係と
Bの先祖からBの子孫への関係を削除
(ただし自己参照を除く)
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

A

C

B

B

C

B

D

D

B

E

C

C

D

D

E

E

B

E

A

B

D

C

E

BをCの子へ移動
→Bを子孫とする関係と
Bの先祖からBの子孫への関係を削除
(ただし自己参照を除く)
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

B

A

A

A

A

D

A

C

A

E

B

B

C

B

C

B

D

C

D

D

B

E

C

E

C

C

D

D

E

E

B

E

A

B

D

C

E

BをCの子へ移動
→Cを子孫とする移動先サブツリーから
孤立中のBのツリーのノードへの全経路を
cross joinで生成する
解決策 : 閉包テーブルモデル
node

先祖

子孫

A

A

A

D

D

A

B

E

E

A

C

C

A

D

D

A

E

B

B

B

D

B

E

C

B

C

C

C

D

C

E

B

E

A

C

B

BをCの子へ移動

D

E
まとめ


隣接リスト





経路列挙






シンプルかつツリー取得可能
ジェイウォーク

入れ子集合







シンプル
ツリー取得、操作が苦手

子孫、先祖ツリーの取得が可能
ノードの削除で子ノードが自動で昇格する
ツリー操作が死ぬほど複雑

閉包テーブル





入れ子集合に比べれば(比べればと言う程度)シンプル
先祖、子孫の取得、サブツリーごと削除、子の昇格、ツリー移動な
ど大体の事が出来る
その代わりツリー構造管理テーブルが必要
パステーブルのデータ量がめっちゃ増える
ナイーブツリー(素朴な木)


ツリー表現の解決策としては素朴過ぎると
言う意味



素朴故に多用される。



ただし常にナイーブツリー is 悪ではない



ツリーを表現する手法は他にもあるんだよ、
ということを覚えておきましょう
アンチパターンを用いていい場合


隣接リストでアプリの要件を満たせる場合
 実際シンプル
 その他の手法(特に入れ子集合)はそれなりの複

雑さを持ち込むことになる
 複雑さ is バグの元


利用DB製品が再帰クエリ構文をサポートす
る場合
 ぽすぐれ、SQLServer、Oracle、DB2

 再帰クエリなら隣接リストでもツリー取得可
おわり

More Related Content

SQLアンチパターン - ナイーブツリー