32

我有一个具有自引用外键关系的模型:

class Person(TimeStampedModel):
    name = models.CharField(max_length=32)
    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

现在我想为一个人获取所有多层次的孩子。如何为它编写 Django 查询?它需要表现得像递归函数。

4

9 回答 9

42

您始终可以在模型中添加递归函数:

编辑:根据 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

于 2011-01-18T16:25:17.870 回答
18

您应该阅读有关修改前序树遍历的内容。这是django的实现。 https://github.com/django-mptt/django-mptt/

于 2011-01-18T14:56:47.303 回答
8

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
于 2016-04-19T12:23:27.973 回答
5

如果你知道你的树的最大深度,你可以尝试这样的事情(未经测试):

Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))
于 2011-01-18T21:50:56.823 回答
3

class Person(TimeStampedModel): name = 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() ,直到找不到更多数据/子代。

于 2020-09-08T14:57:59.177 回答
1

我知道这是旧的,但有人可能会得到帮助。

     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

然后只需在模型上将其设为 a property(或 acached_property如果对您有用),以便可以在任何实例上调用它。

于 2018-01-11T06:26:08.320 回答
0

我有一个非常相似的业务问题,其中给定一个团队成员,我应该找出他手下的完整团队。但是拥有大量员工使得递归解决方案非常低效,而且我的 API 从服务器收到超时错误。

接受的解决方案采用节点,进入它的第一个子节点并深入到层次结构的底部。然后再次回到第二个孩子(如果存在),然后再次下降到底部。简而言之,它会一一探索所有节点并将所有成员附加到一个数组中。这会导致大量的数据库调用,如果要探索大量节点,则应避免。我想出的解决方案是逐层获取节点。db 调用的数量等于层数。查看此SO 链接以获取解决方案。

于 2016-10-08T13:23:16.060 回答
0

我还将在 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)

注意:以下解决方案与之前的其他答案一样慢。每次递归到其子/父时,我都检查了数据库命中。(如果有人可以通过一些优化和缓存进一步改进,这会更好,也许在查询之前预取相关数据)。同时,mptt更实用。

于 2018-06-24T19:05:58.413 回答
0

这是我为获取通过其“子”字段连接的帖子的所有步骤而写的内容

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) 
于 2021-10-13T17:14:47.733 回答