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

Post on 28-May-2015

11.157 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

社内勉強会資料 追記: 2013-10-31 ついったで指摘( https://twitter.com/akuraru/status/395822183777202176 )を受けたので入れ子集合のノード追加の説明の所を修正しました。

TRANSCRIPT

ナイーブツリー

SQLアンチパターンその2 素 朴 な 木

ナイーブツリー is

直近の親のみを参照する事でツリー構造を表現する SQLアンチパターン

シナリオ

A「ブログのコメント欄さ」

B「はい」

A「よく議論やってるじゃん?」

B「ですね」

A「スレッド形式で表示できたら分かりやすいと思うんよね」

B「考えてみます」

シナリオ

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

id comment

1 たけのこ派駆逐したい

2 きのこ派怖いわー

3 >>1 同意

4 >>2 たけのこ汚い

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い

シナリオ

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

id comment

1 たけのこ派駆逐したい

2 きのこ派怖いわー

3 >>1 同意

4 >>2 たけのこ汚い

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い

シナリオ

B「こうすればいいか」

id parent comment

1 null たけのこ派駆逐したい

2 1 きのこ派怖いわー

3 1 >>1 同意

4 2 >>2 たけのこ汚い

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い

シナリオ

B「こうすればいいか」

id parent comment

1 null たけのこ派駆逐したい

2 1 きのこ派怖いわー

3 1 >>1 同意

4 2 >>2 たけのこ汚い

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い

素 朴 な 木

ナイーブツリー

Bの主張

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

Bの主張

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

ツリーの参照は?

コメント数の数えるとか集約系の関数は?

非葉ノードの削除は?サブツリーの昇格は?

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

(サブ)ツリーを引っこ抜けない

ツリー全体

あるいはあるコメントのサブツリー

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

(サブ)ツリーを引っこ抜けない

ツリー全体

あるいはあるコメントのサブツリー

たけのこ派駆逐したい select

c1.* from comm c1

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

(サブ)ツリーを引っこ抜けない

ツリー全体

あるいはあるコメントのサブツリー

たけのこ派駆逐したい

きのこ派怖いわー

select c1.*, c2.* from comm c1 left outer join comm c2 on c1.id = c2.parent コメント1を親に持つコメント

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

(サブ)ツリーを引っこ抜けない

ツリー全体

あるいはあるコメントのサブツリー

たけのこ派駆逐したい

きのこ派怖いわー

>>2 たけのこ汚い

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 コメント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ズパーンとはいかない

削除前に自分の子を取得し、子の親を自分の親へ付け替えた上で削除する必要がある

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

非葉ノード削除とそのサブツリーの昇格

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い

←削除したい

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

非葉ノード削除とそのサブツリーの昇格

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い

←削除したい

←子の親を

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

非葉ノード削除とそのサブツリーの昇格

たけのこ派駆逐したい

きのこ派怖いわー

>>1 同意

>>2 たけのこ汚い

←削除したい

←自分の親へ更新

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

非葉ノード削除とそのサブツリーの昇格

たけのこ派駆逐したい

>>1 同意

>>2 たけのこ汚い

←削除

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

非葉ノード削除とそのサブツリーの昇格

たけのこ派駆逐したい

>>1 同意

>>2 たけのこ汚い

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

まとめ

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

サブツリー一括の削除や移動はまだしもノードの昇格とかちょっと凝った操作を実現するのはつらい。

B「僕は…どうしたらよかったんだ…」

解決策

代替ツリーモデルを使いましょう

ツリーを表現する他の方法

経路列挙モデル

入れ子集合モデル

閉包テーブルモデル

解決策 : 経路列挙モデル

id path comment

1 1/ たけのこ派駆逐したい

2 1/2 きのこ派怖いわー

3 1/3 >>1 同意

4 1/2/4 >>2 たけのこ汚い

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

解決策 : 経路列挙モデル

メリット サブツリーの一括取得ができる

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 たけのこ汚い

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

2に子を追加

解決策 : 経路列挙モデル

id path comment

1 1/ たけのこ派駆逐したい

2 1/2 きのこ派怖いわー

3 1/3 >>1 同意

4 1/2/4 >>2 たけのこ汚い

5 1/2/5 きのこfudやめろ たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い きのこfudやめろ

解決策 : 経路列挙モデル

id path comment

1 1/ たけのこ派駆逐したい

2 1/2 きのこ派怖いわー

3 1/3 >>1 同意

4 1/2/4 >>2 たけのこ汚い

5 1/2/5 きのこfudやめろ たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い きのこfudやめろ

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

解決策 : 入れ子集合モデル

id left right comment

1 1 8 たけのこ派駆逐したい

2 2 5 きのこ派怖いわー

3 6 7 >>1 同意

4 3 4 >>2 たけのこ汚い

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

解決策 : 経路列挙モデル

Left? Right? Left : その子ノードの持つどのLR値よりも小さい値

Right: その子ノードの持つどのLR値よりも大きな値

深さ優先探索で求まる

メリット 子孫、先祖ノードの一括取得ができる

ノードを削除すると自動的に子ノードが親ノードの子へ昇格する

デメリット ノードの追加や更新(移動)が複雑過ぎる

LR値の再計算が必要

隣接リストでは簡単な直近の子の取得がだるい

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3 4

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3 4

5

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

5

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

7 5

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

サブツリーの どのLR値より小さい

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

サブツリーの どのLR値より大きい

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

1の子孫ツリーの取得 →L値が1~8のコメント

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

1の子孫ツリーの取得 →L値が1~8のコメント

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

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

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

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

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

2にノードを追加

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

2にノードを追加

きのこfudやめろ

5 6

↑ L:R = 5:6を追加したい

さっきの深さ優先探索より 2:5の子に追加するのでこのノードのLR値は5:6になるはず (親のノードのR値を追加しようとするノードのL値にする)

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

6

4

8

7 5

2にノードを追加

きのこfudやめろ

5 6

↑ 追加されたノード以降のLR値を再計算(+2する)

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

8

4

10

9 7

2にノードを追加

きのこfudやめろ

5 6

解決策 : 入れ子集合モデル

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

1

2

3

8

4

10

9 7

2にノードを追加

きのこfudやめろ

5 6

↑ 追加

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

id comment

1 たけのこ派駆逐したい

2 きのこ派怖いわー

3 >>1 同意

4 >>2 たけのこ汚い

たけのこ駆逐したい

>>1 同意 きのこ派怖いわー

>>2 たけのこ汚い

先祖 子孫

1 1

1 2

1 4

1 3

2 2

2 4

3 3

4 4

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

ノード同士の関係性を定義するテーブルを追加する 直近の親子関係だけじゃなく、全体のパスを持つ

離れたノードの親子関係も 自分自身を参照するパスも含める

メリット ツリーの取得、移動、ツリーの削除、ツリーの昇格、マルチルート、複数の親への所属などとにかく柔軟

入れ子集合ほどは複雑じゃない

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

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

node

A

B

C

D

A

C B

D

先祖 子孫

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

A D

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

A D

B B

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

A D

B B

B D

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

A D

B B

B D

C C

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

A D

B B

B D

C C

D D

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

A D

B B

B D

C C

D D 直近だけでなく全てのノードの親子関係を定義 パステーブルの1行は上図の矢印1本に相当する

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

A D

B B

B D

C C

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

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

node

A

B

C

D

A

C B

D

先祖 子孫

A A

A B

A C

A D

B B

B D

C C

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

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

node

A

B

C

D

E

A

C B

D

先祖 子孫

A A

A B

A C

A D

B B

B D

C C

D D Bの子としてEを追加 →自己参照追加 →Bを子孫として持つ先祖の子孫にEを追加

E

E E

A E

B E

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

node

A

B

C

D

E

A

C B

D

先祖 子孫

A A

A B

A C

A D

A E

B B

B D

B E

C C

D D

E E Bの子としてEを追加 →自己参照追加 →Bを子孫として持つ先祖の子孫にEを追加

E

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

node

A

B

C

D

E

A

C B

D

先祖 子孫

A A

A B

A C

A D

A E

B B

B D

B E

C C

D D

E E Bのツリーをまるごと削除 →Bを子孫とする関係を削除

E

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

node

A

B

C

D

E

A

C B

D

先祖 子孫

A A

A B

A C

A D

A E

B B

B D

B E

C C

D D

E E Bのツリーをまるごと削除 →Bを子孫とする関係を削除 →Bの子孫を子孫とする関係を削除 (Bを先祖とするノードを子孫として持つ関係)

E

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

node

A

B

C

D

E

A

C B

D

先祖 子孫

A A

A C

C C

Bのツリーをまるごと削除 →Bを子孫とする関係を削除 →Bの子孫を子孫とする関係を削除 (Bを先祖とするノードを子孫として持つ関係)

E

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

node

A

B

C

D

E

Bを削除 →Bを子孫とする関係を削除

先祖 子孫

A A

A B

A C

A D

A E

B B

B D

B E

C C

D D

E E

A

C B

D E

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

node

A

B

C

D

E

Bを削除 →Bを子孫とする関係を削除 →Bを先祖とする関係を削除

先祖 子孫

A A

A B

A C

A D

A E

B B

B D

B E

C C

D D

E E

A

C B

D E

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

node

A

B

C

D

E

Bを削除 → C, Eが自動的にAの子に昇格 →Bを子孫とする関係を削除 →Bを先祖とする関係を削除

先祖 子孫

A A

A C

A D

A E

C C

D D

E E

A

C B

D E

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

node

A

B

C

D

E

A

C B

D

先祖 子孫

A A

A B

A C

A D

A E

B B

B D

B E

C C

D D

E E

BをCの子へ移動 →Bを子孫とする関係と Bの先祖からBの子孫への関係を削除 (ただし自己参照を除く)

E

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

node

A

B

C

D

E

A

C B

D

先祖 子孫

A A

A C

B B

B D

B E

C C

D D

E E

BをCの子へ移動 →Bを子孫とする関係と Bの先祖からBの子孫への関係を削除 (ただし自己参照を除く)

E

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

node

A

B

C

D

E

A

C B

D

先祖 子孫

A A

A C

B B

B D

B E

C C

D D

E E

BをCの子へ移動 →Cを子孫とする移動先サブツリーから 孤立中のBのツリーのノードへの全経路を cross joinで生成する

E

A B

A D

A E

C B

C D

C E

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

node

A

B

C

D

E

A

C

B

D

先祖 子孫

A A

A B

A C

A D

A E

B B

B D

B E

C B

C C

C D

C E

BをCの子へ移動 E

D D

E E

まとめ

隣接リスト シンプル ツリー取得、操作が苦手

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

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

閉包テーブル 入れ子集合に比べれば(比べればと言う程度)シンプル 先祖、子孫の取得、サブツリーごと削除、子の昇格、ツリー移動な

ど大体の事が出来る その代わりツリー構造管理テーブルが必要 パステーブルのデータ量がめっちゃ増える

ナイーブツリー(素朴な木)

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

素朴故に多用される。

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

ツリーを表現する手法は他にもあるんだよ、ということを覚えておきましょう

アンチパターンを用いていい場合

隣接リストでアプリの要件を満たせる場合

実際シンプル

その他の手法(特に入れ子集合)はそれなりの複雑さを持ち込むことになる

複雑さ is バグの元

利用DB製品が再帰クエリ構文をサポートする場合

ぽすぐれ、SQLServer、Oracle、DB2

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

おわり

top related