Django自递归外键过滤器查询所有孩子
Posted
技术标签:
【中文标题】Django自递归外键过滤器查询所有孩子【英文标题】:Django self-recursive foreignkey filter query for all childs 【发布时间】:2011-06-11 03:40:57 【问题描述】:我有一个具有自引用外键关系的模型:
class Person(TimeStampedModel):
name = models.CharField(max_length=32)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
现在我想为一个人获取所有多层次的孩子。如何为它编写 Django 查询?它需要表现得像递归函数。
【问题讨论】:
【参考方案1】:这是我为获取通过其“子”字段连接的帖子的所有步骤而写的内容
steps = Steps.objects.filter(post = post)
steps_ids = steps.values_list('id', flat=True)
递归地获取“步骤”的所有孩子:
objects = Steps.objects.filter(child__in=steps_ids)
while objects:
steps = steps | objects
steps_ids = objects.values_list('id', flat=True)
objects = Steps.objects.filter(child__in=steps_ids)
【讨论】:
【参考方案2】:您始终可以在模型中添加递归函数:
编辑:根据 SeomGi Han 更正
def get_all_children(self, include_self=True):
r = []
if include_self:
r.append(self)
for c in Person.objects.filter(parent=self):
_r = c.get_all_children(include_self=True)
if 0 < len(_r):
r.extend(_r)
return r
(如果你有很多递归或数据,请不要使用这个......)
仍然按照 errx 的建议推荐 mptt。
编辑:2021 年,因为这个答案仍然受到关注:/
改用django-tree-queries!
【讨论】:
应该是 get_all_children() 作为函数调用,你需要使用 r = r + c.get_all_children() 否则你会得到嵌套列表。我似乎无权自己编辑此内容 根据评论更新了代码。如果每个人都被允许编辑所有帖子,我们会有一个大问题:) 我认为这在内部函数调用中也应该有include_self=True
。否则,在每次递归时,我们只会再下降一层,而实际上从未向r
添加任何内容。进行更改后,它才对我正常工作。
您的解决方案提供了一个很好的概念,但未经测试。最后,r 是一个嵌套的空列表。请考虑修复它..
***.com/questions/42676085/…【参考方案3】:
类人(TimeStampedModel): 名称 = models.CharField(max_length=32) parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
def get_children(self):
children = list()
children.append(self)
for child in self.children.all():
children.extend(children.get_children())
return children
get_children() 将使用相关名称获取实例的所有子节点,然后如果递归找到,它将在子节点上调用 get_children(),直到找不到更多数据/子节点。
【讨论】:
这个答案可以通过添加解释来改进。 完成了,现在可以了吗?【参考方案4】:我知道这是旧的,但有人可能会得到帮助。
def get_all_children(self, container=None):
if container is None:
container = []
result = container
for child in self.children.all():
result.append(child)
if child.children.count() > 0:
child.get_all_children(result)
return result
然后在模型上简单地将其设为property
(或cached_property
,如果这对您有用),以便可以在任何实例上调用它。
【讨论】:
这并没有回答提问者,“为一个人获取所有 multi level 孩子”......这实际上只会得到一个人的直接子代(即一级)。 谢谢@Yeo 我一定误解了这个问题。我刚刚用我的一个项目中的类似 sn-p 更新了我的回复。【参考方案5】:我还将在 QuerySet 中编写,因为这将允许您链接它们。 我将为所有孩子和所有父母的检索提供答案。
class PersonQuerySet(QuerySet):
def descendants(self, person):
q = Q(pk=person.pk)
for child in person.children.all():
q |= Q(pk__in=self.descendants(child))
return self.filter(q)
def ancestors(self, person):
q = Q(pk=person.pk)
if person.parent:
q |= Q(pk__in=self.ancestors(person.parent))
return self.filter(q)
现在我们需要将PersonQuerySet
设置为经理。
class Person(TimeStampedModel):
name = models.CharField(max_length=32)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
people = PersonQuerySet.as_manager()
这是最后的查询。
albert_einstein = Person.people.get(name='Albert Einstein')
bernhard_einstein = Person.peole.get(name='Bernhard Caesar Einstein')
einstein_folks = Person.people.descendants(albert_einstein).ancestors(bernhard_einstein)
注意: 以下解决方案与之前的其他其他答案一样慢。每次递归到其子/父时,我都检查了数据库命中。 (如果有人可以通过一些优化和缓存进一步改进,这会更好,也许在查询之前 prefetch 相关数据)。同时,mptt更实用。
【讨论】:
【参考方案6】:我有一个非常相似的business problem,其中给定一个团队成员,我应该找出他手下的完整团队。但是拥有大量员工使得递归解决方案非常低效,而且我的 API 从服务器收到超时错误。
接受的解决方案采用节点,转到它的第一个子节点,然后深入到层次结构的底部。然后再次回到第二个孩子(如果存在),然后再次下降到底部。简而言之,它会一一探索所有节点并将所有成员附加到一个数组中。这会导致大量的数据库调用,如果要探索大量节点,则应避免这种情况。我想出的解决方案是逐层获取节点。 db 调用的数量等于层数。看看这个SO link 的解决方案。
【讨论】:
【参考方案7】:sunn0 的建议是个好主意,但 get_all_children() 返回奇怪的结果。它返回类似 [Person1, [Person3, Person4], []] 的内容。应该改成下面这样。
def get_all_children(self, include_self=True):
r = []
if include_self:
r.append(self)
for c in Person.objects.filter(parent=self):
_r = c.get_all_children(include_self=True)
if 0 < len(_r):
r.extend(_r)
return r
【讨论】:
【参考方案8】:如果你知道你的树的最大深度,你可以尝试这样的事情(未经测试):
Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))
【讨论】:
【参考方案9】:您应该阅读修改后的预序树遍历。 这是django的实现。 https://github.com/django-mptt/django-mptt/
【讨论】:
是否需要使用第三方解决方案?有没有其他办法?? @Ashan:你可以随时阅读它,例如这里:dev.mysql.com/tech-resources/articles/hierarchical-data.html 自己写代码以上是关于Django自递归外键过滤器查询所有孩子的主要内容,如果未能解决你的问题,请参考以下文章