Django中使用 Closure Table 储存无限分级数据
这篇文章给大家介绍Django中使用ClosureTable储存无限分级数据,具体内容如下所述:
起步
对于数据量大的情况(比如用户之间有邀请链,有点三级分销的意思),就要用到closuretable的结构来进行存储。那么在Django中如何处理这个结构的模型呢?
定义模型
至少是要两个模型的,一个是存储分类,一个储存分类之间的关系:
classCategory(models.Model):
name=models.CharField(max_length=31)
def__str__(self):
returnself.name
classCategoryRelation(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()
classMeta:
unique_together=("ancestor","descendant")
数据操作
获得所有后代节点
classCategory(models.Model):
...
defget_descendants(self,include_self=False):
"""获得所有后代节点"""
kw={
'descendants__ancestor':self
}
ifnotinclude_self:
kw['descendants__distance__gt']=0
qs=Category.objects.filter(**kw).order_by('descendants__distance')
returnqs获得直属下级
classCategory(models.Model):
...
defget_children(self):
"""获得直属下级"""
qs=Category.objects.filter(descendants__ancestor=self,descendants__distance=1)
returnqs
节点的移动
节点的移动是比较难的,在[https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/][1]中讲述了,利用django能够执行原生的sql语句进行:
defadd_child(self,child):
"""将某个分类加入本分类,"""
ifCategoryRelation.objects.filter(ancestor=child,descendant=self).exists()\
orCategoryRelation.objects.filter(ancestor=self,descendant=child,distance=1).exists():
"""child不能是self的祖先节点or它们已经是父子节点"""
return
#如果表中不存在节点自身数据
ifnotCategoryRelation.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"""
DELETEa
FROM
{table_name}ASa
JOIN{table_name}ASdONa.descendant_id=d.descendant_id
LEFTJOIN{table_name}ASxONx.ancestor_id=d.ancestor_id
ANDx.descendant_id=a.ancestor_id
WHERE
d.ancestor_id={child.id}
ANDx.ancestor_idISNULL;
""")
cursor.execute(f"""
INSERTINTO{table_name}(ancestor_id,descendant_id,distance)
SELECTsupertree.ancestor_id,subtree.descendant_id,
supertree.distance+subtree.distance+1
FROM{table_name}ASsupertreeJOIN{table_name}ASsubtree
WHEREsubtree.ancestor_id={child.id}
ANDsupertree.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]
完整代码
classCategory(models.Model):
name=models.CharField(max_length=31)
def__str__(self):
returnself.name
defget_descendants(self,include_self=False):
"""获得所有后代节点"""
kw={
'descendants__ancestor':self
}
ifnotinclude_self:
kw['descendants__distance__gt']=0
qs=Category.objects.filter(**kw).order_by('descendants__distance')
returnqs
defget_children(self):
"""获得直属下级"""
qs=Category.objects.filter(descendants__ancestor=self,descendants__distance=1)
returnqs
defget_ancestors(self,include_self=False):
"""获得所有祖先节点"""
kw={
'ancestors__descendant':self
}
ifnotinclude_self:
kw['ancestors__distance__gt']=0
qs=Category.objects.filter(**kw).order_by('ancestors__distance')
returnqs
defget_parent(self):
"""分类仅有一个父节点"""
parent=Category.objects.get(ancestors__descendant=self,ancestors__distance=1)
returnparent
defget_parents(self):
"""分类仅有一个父节点"""
qs=Category.objects.filter(ancestors__descendant=self,ancestors__distance=1)
returnqs
defremove(self,delete_subtree=False):
"""删除节点"""
ifdelete_subtree:
#删除所有子节点
children_queryset=self.get_descendants(include_self=True)
forchildinchildren_queryset:
CategoryRelation.objects.filter(Q(ancestor=child)|Q(descendant=child)).delete()
child.delete()
else:
#所有子节点移到上级
parent=self.get_parent()
children=self.get_children()
forchildinchildren:
parent.add_chile(child)
#CategoryRelation.objects.filter(descendant=self,distance=0).delete()
CategoryRelation.objects.filter(Q(ancestor=self)|Q(descendant=self)).delete()
self.delete()
defadd_child(self,child):
"""将某个分类加入本分类,"""
ifCategoryRelation.objects.filter(ancestor=child,descendant=self).exists()\
orCategoryRelation.objects.filter(ancestor=self,descendant=child,distance=1).exists():
"""child不能是self的祖先节点or它们已经是父子节点"""
return
#如果表中不存在节点自身数据
ifnotCategoryRelation.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"""
DELETEa
FROM
{table_name}ASa
JOIN{table_name}ASdONa.descendant_id=d.descendant_id
LEFTJOIN{table_name}ASxONx.ancestor_id=d.ancestor_id
ANDx.descendant_id=a.ancestor_id
WHERE
d.ancestor_id={child.id}
ANDx.ancestor_idISNULL;
""")
cursor.execute(f"""
INSERTINTO{table_name}(ancestor_id,descendant_id,distance)
SELECTsupertree.ancestor_id,subtree.descendant_id,
supertree.distance+subtree.distance+1
FROM{table_name}ASsupertreeJOIN{table_name}ASsubtree
WHEREsubtree.ancestor_id={child.id}
ANDsupertree.descendant_id={self.id};
""")classCategoryRelation(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()
classMeta:
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中使用ClosureTable储存无限分级数据,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!