Mais conteúdo relacionado いまさらツリー構造2. self.inspect
[1] https://github.com/muramurasan/okuribito [2] http://poject.herokuapp.com/
➢ gemのロゴを書いたり…
➢ たまにQiitaの記事を書いたり…
➢ 新卒で某SIerに就職
➢ 渋谷の企業にてRuby on Railsを学ぶ
➢ 今はお客さんに謝りに行くお仕事
Name : Yuya Taki
GitHub : yuyasat
Qiita : yuyasat
[1]
若輩者ですので、何卒優しくご教授いただければと
思います。
(commitはしていない)
➢ ○よ○よ風ゲームをReact.jsで実装し
たり
[2]
7. RDBにおける木構造の実装方法
➢ 隣接リストモデル
○ 今回実装したのがこれ。
○ SQLアンチパターン第二章ナイーブツリー。
➢ 経路列挙モデル
○ ancestryでの実装。
➢ 入れ子集合(整数)モデル
➢ 入れ子区間(実数)モデル
○ ミック「データ型の制度という物理制約さえなければRDBで階層構造を扱う方法論として
は、検索/更新のパフォーマンスとモデルの簡潔さの双方において最も優れている」[1]
○ でも複雑さは一番大きい。
➢ 閉包テーブルモデル
○ 経路情報をもつテーブルを作る。
節点(node)
葉(leaf)
枝(edge)
根(root)
[1] 達人に学ぶDB設計徹底指南書p291
8. 各実装の特徴
設計 テーブル数 子孫アクセス ツリーへのク
エリ実行
挿入 削除 参照整合性維
持
隣接リスト 1 簡単 難しい
※再帰クエリ
を使えば簡単
簡単 簡単
※場合による
可能
経路列挙 1 簡単 簡単 簡単 簡単 不可
入れ子 1 難しい 難しい 難しい 難しい 不可
閉包テーブル 2 簡単 簡単 簡単 簡単 可能
[1] SQLアンチパターンp30 [2] SQLアンチパターンp18
[1]
アンチパターンといいつつ、再帰処理ができれば用いても良い [2]
経路列挙モデルが跋扈してたので原点に戻って隣接リストモデルで実装してみた。
10. 子孫(サブツリー)の取得
➢ rubyで再帰処理で解決!
def descendants(category = self, array = [], include_self: true, only_id: true)
array << (only_id ? self.id : self) if include_self && id == category.id
return array + [only_id ? category.id : category] if category.children.blank?
category.children.eager_load(:children).each do |cat|
array << (only_id ? cat.id : cat)
descendants(cat, array)
end
array
end
➢ SQLでもかける(後述)
11. 先祖を取得するメソッドと子孫を取得するメソッド
class Category < ApplicationRecord
belongs_to :parent, class_name: 'Category', foreign_key: :parent_id
has_many :children, class_name: 'Category', foreign_key: :parent_id
def ancestors(category = self, result = [], include_self: true, only_id: true)
return result + [only_id ? category.id : category] if category.root?
ancestors(category.parent, result, only_id: only_id) +
(!include_self && id == category.id ? [] : [only_id ? category.id : category])
end
def descendants(category = self, array = [], include_self: true, only_id: true)
array << (only_id ? self.id : self) if include_self && id == category.id
return array + [only_id ? category.id : category] if category.children.blank?
category.children.eager_load(:children).each do |cat|
array << (only_id ? cat.id : cat)
descendants(cat, array)
end
array
end
end
先祖を取得
子孫を取得
12. ancestryのインスタンスメソッドとの対応
ancestry 隣接リストモデル
parent parent
parent_id parent_id
root ancestors(only_id: false).first
root_id ancestors.first
root? parent_id == 0
ancestors ancestors(include_self: false, only_id: false)
ancestors? ancestors.length > 1
ancestor_ids ancestors(include_self: false)
path ancestors(only_id: false)
path_ids ancestors
children children
child_ids children.pluck(:id)
has_parent? parent_id != 0 or self.class.exists?(id: parent_id)
has_children? children.exists?
childless? !children.exists?
https://github.com/stefankroes/ancestry
ancestry 隣接リストモデル
siblings parent&.children || self.class.ro
sibling_ids siblings.pluck(:id)
has_siblings? siblings.exists?
only_child? siblings.count == 1
descendants
descendants(include_self: false,
only_id: false)
descendant_ids descendants(include_self: false)
subtree descendants(only_id: false)
subtree_ids descendants
depth ancestors.count - 1
13. With `WITH RECURSIVE`
def ancestors(include_self: true)
ids = Category.find_by_sql(<<-SQL).map(&:id) - (include_self ? [] : [id])
WITH RECURSIVE ancestors(id, parent_id) as (
SELECT
categories.id,
categories.parent_id
FROM
categories
WHERE
categories.id = #{id}
UNION ALL
SELECT
categories.id,
categories.parent_id
FROM
ancestors,
categories
WHERE
ancestors.parent_id = categories.id
)
SELECT id FROM ancestors;
SQL
self.class.where(id: ids).order(ids.map { |id| "categories.id = #{id} desc" })
end
14. With `WITH RECURSIVE`
def descendants(include_self: true)
ids = Category.find_by_sql(<<-SQL).map(&:id) - (include_self ? [] : [id])
WITH RECURSIVE children(id, parent_id) as (
SELECT
categories.id,
categories.parent_id
FROM
categories
WHERE
categories.id = #{id}
UNION ALL
SELECT
categories.id,
categories.parent_id
FROM
categories,
children
WHERE
children.id = categories.parent_id
)
SELECT id FROM children;
SQL
self.class.where(id: ids).order(ids.map { |id| "categories.id = #{id} desc" })
end