Django中使用 Closure Table 儲存無限分級數據

這篇文章主要介紹了Django中使用 Closure Table 儲存無限分級數據,需要的朋友可以參考下

Django中使用 Closure Table 儲存無限分級數據

起步

上一篇討論瞭如何用數據庫存儲無限分級的數據。對於數據量大的情況(比如用戶之間有邀請鏈,有點三級分銷的意思),就要用到 closure table 的結構來進行存儲。那麼在 Django 中如何處理這個結構的模型呢?

定義模型

至少是要兩個模型的,一個是存儲分類,一個儲存分類之間的關係:

class Category(models.Model):
  name = models.CharField(max_length=31)
  def __str__(self):
    return self.name
class CategoryRelation(models.Model):
  ancestor = models.ForeignKey(Category, null=True, related_name='ancestors', on_delete=models.SET_NULL, db_constraint=False, verbose_name='祖先')
  descendant = models.ForeignKey(Category,null=True, related_name='descendants', on_delete=models.SET_NULL,
                  db_constraint=False, verbose_name='子孫')
  distance = models.IntegerField()
  class Meta:
    unique_together = ("ancestor", "descendant")

數據操作

獲得所有後代節點

class Category(models.Model):
  ...
  def get_descendants(self, include_self=False):
    """獲得所有後代節點"""
    kw = {
      'descendants__ancestor' : self
    }
    if not include_self:
      kw['descendants__distance__gt'] = 0
    qs = Category.objects.filter(**kw).order_by('descendants__distance')
    return qs獲得直屬下級
class Category(models.Model):
  ...
  def get_children(self):
    """獲得直屬下級"""
    qs = Category.objects.filter(descendants__ancestor=self, descendants__distance=1)
    return qs

節點的移動

節點的移動是比較難的,在 [ https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/][1 ] 中講述了,利用django能夠執行原生的sql語句進行:

def add_child(self, child):
    """將某個分類加入本分類,"""
    if CategoryRelation.objects.filter(ancestor=child, descendant=self).exists() \
        or CategoryRelation.objects.filter(ancestor=self, descendant=child, distance=1).exists():
      """child不能是self的祖先節點 or 它們已經是父子節點"""
      return
    # 如果表中不存在節點自身數據
    if not CategoryRelation.objects.filter(ancestor=child, descendant=child).exists():
      CategoryRelation.objects.create(ancestor=child, descendant=child, distance=0)
    table_name = CategoryRelation._meta.db_table
    cursor = connection.cursor()
    cursor.execute(f"""
      DELETE a
      FROM
        {table_name} AS a
      JOIN {table_name} AS d ON a.descendant_id = d.descendant_id
      LEFT JOIN {table_name} AS x ON x.ancestor_id = d.ancestor_id
      AND x.descendant_id = a.ancestor_id
      WHERE
        d.ancestor_id = {child.id}
      AND x.ancestor_id IS NULL;
    """)
    cursor.execute(f"""
    INSERT INTO {table_name} (ancestor_id, descendant_id, distance)
    SELECT supertree.ancestor_id, subtree.descendant_id,
    supertree.distance+subtree.distance+1
    FROM {table_name} AS supertree JOIN {table_name} AS subtree
    WHERE subtree.ancestor_id = {child.id}
    AND supertree.descendant_id = {self.id};
    """)

 節點刪除

節點刪除有兩種操作,一個是將所有子節點也刪除,另一個是將自己點移到上級節點中。

擴展閱讀

[ https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/][2 ]
[ http://technobytz.com/closure_table_store_hierarchical_data.html][3 ]

完整代碼

class Category(models.Model):
name = models.CharField(max_length=31)
def __str__(self):
  return self.name
def get_descendants(self, include_self=False):
  """獲得所有後代節點"""
  kw = {
    'descendants__ancestor' : self
  }
  if not include_self:
    kw['descendants__distance__gt'] = 0
  qs = Category.objects.filter(**kw).order_by('descendants__distance')
  return qs
def get_children(self):
  """獲得直屬下級"""
  qs = Category.objects.filter(descendants__ancestor=self, descendants__distance=1)
  return qs
def get_ancestors(self, include_self=False):
  """獲得所有祖先節點"""
  kw = {
    'ancestors__descendant': self
  }
  if not include_self:
    kw['ancestors__distance__gt'] = 0
  qs = Category.objects.filter(**kw).order_by('ancestors__distance')
  return qs
def get_parent(self):
  """分類僅有一個父節點"""
  parent = Category.objects.get(ancestors__descendant=self, ancestors__distance=1)
  return parent
def get_parents(self):
  """分類僅有一個父節點"""
  qs = Category.objects.filter(ancestors__descendant=self, ancestors__distance=1)
  return qs
def remove(self, delete_subtree=False):
  """刪除節點"""
  if delete_subtree:
    # 刪除所有子節點
    children_queryset = self.get_descendants(include_self=True)
    for child in children_queryset:
      CategoryRelation.objects.filter(Q(ancestor=child) | Q(descendant=child)).delete()
      child.delete()
  else:
    # 所有子節點移到上級
    parent = self.get_parent()
    children = self.get_children()
    for child in children:
      parent.add_chile(child)
    # CategoryRelation.objects.filter(descendant=self, distance=0).delete()
    CategoryRelation.objects.filter(Q(ancestor=self) | Q(descendant=self)).delete()
    self.delete()
def add_child(self, child):
  """將某個分類加入本分類,"""
  if CategoryRelation.objects.filter(ancestor=child, descendant=self).exists() \
      or CategoryRelation.objects.filter(ancestor=self, descendant=child, distance=1).exists():
    """child不能是self的祖先節點 or 它們已經是父子節點"""
    return
  # 如果表中不存在節點自身數據
  if not CategoryRelation.objects.filter(ancestor=child, descendant=child).exists():
    CategoryRelation.objects.create(ancestor=child, descendant=child, distance=0)
  table_name = CategoryRelation._meta.db_table
  cursor = connection.cursor()
  cursor.execute(f"""
    DELETE a
    FROM
      {table_name} AS a
    JOIN {table_name} AS d ON a.descendant_id = d.descendant_id
    LEFT JOIN {table_name} AS x ON x.ancestor_id = d.ancestor_id
    AND x.descendant_id = a.ancestor_id
    WHERE
      d.ancestor_id = {child.id}
    AND x.ancestor_id IS NULL;
  """)
  cursor.execute(f"""
  INSERT INTO {table_name} (ancestor_id, descendant_id, distance)
  SELECT supertree.ancestor_id, subtree.descendant_id,
  supertree.distance+subtree.distance+1
  FROM {table_name} AS supertree JOIN {table_name} AS subtree
  WHERE subtree.ancestor_id = {child.id}
  AND supertree.descendant_id = {self.id};
  """)class CategoryRelation(models.Model): ancestor = models.ForeignKey(Category, null=True, related_name='ancestors', on_delete=models.SET_NULL, db_constraint=False, verbose_name='祖先') descendant = models.ForeignKey(Category,null=True, related_name='descendants', on_delete=models.SET_NULL, db_constraint=False, verbose_name='子孫') distance = models.IntegerField()
class Meta:
  unique_together = ("ancestor", "descendant")[1]: https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/
 [2]: https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/
 [3]: http://technobytz.com/closure_table_store_hierarchical_data.html

總結

以上所述是小編給大家介紹的Django中使用 Closure Table 儲存無限分級數據,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對神馬文庫網站的支持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章