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アンチパターン - ナイーブツリー

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy