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自递归外键过滤器查询所有孩子的主要内容,如果未能解决你的问题,请参考以下文章

Django,从反向外键查询添加数据(外键加入过滤器)

Django查询集过滤器基于孩子的数量

Django:每个外键返回一个过滤对象

Django admin 查询集通过外键向后关系过滤

基于 Django 查询集中外键字段的 .count() 进行过滤

Django反向过滤查询集外键示例不起作用