16

有没有人有一个有效的算法来检索一个 mptt 查询集的所有祖先?到目前为止,我能想到的最好的事情是这样的:

def qs_ancestors(queryset):
    if isinstance(queryset, EmptyQuerySet):
        return queryset
    queryset_aggs = queryset.values_list('tree_id', 'level').annotate(max_lft=Max('lft'), min_rght=Min('rght'))
    new_queryset = queryset.none()
    for tree_id, level, max_lft, min_rght in queryset_aggs:
        ancestors = MyModel.objects.filter(
           tree_id=tree_id,
           level__lt=level, 
           lft__lte=max_lft,
           rght__gte=min_rght,
        )
        new_queryset = ancestors | new_queryset
    return new_queryset

这种方法有两个问题:

  1. 如果有不相邻的分支(即它实际上不起作用),它会失败
  2. 它的效率非常低,因为它最终number_of_trees*number_of_levels在最终查询中包含子句,这些子句会很快变得非常大

我愿意在其他地方缓存祖先,但我想不出一种有效的方法。我考虑添加一个以逗号分隔的祖先 id 列表的字段,然后在GROUP_CONCAT额外的内部执行一个(我在 MySQL 中),但我认为这可能会变得巨大/缓慢。

4

2 回答 2

6

我不得不写一次类似的算法。我有一个显示 MPTT 树的视图,它是一个非常大的树,所以我无法在 HTML 模板中加载它的所有数据。所以我在初始加载时只显示了根节点,并使用 Ajax 加载其他节点。

在我的老板要求我实施“搜索”选项之前,它工作得很好。搜索必须查看所有节点并在找到匹配项时分解树。我花了一段时间才弄清楚这一点,但我终于明白了。这是a提出的解决方案:

from django.db.models import Q

def get_parents(self, qs):
    tree_list = {}
    query = Q()
    for node in qs:
        if node.tree_id not in tree_list:
            tree_list[node.tree_id] = []

        parent =  node.parent.pk if node.parent is not None else None,

        if parent not in tree_list[node.tree_id]:
            tree_list[node.tree_id].append(parent)

            query |= Q(lft__lt=node.lft, rght__gt=node.rght, tree_id=node.tree_id)

    return YourModel.objects.filter(query)

它只需要运行两个查询,第qs一个作为参数传递,最后一个由函数返回的查询集。这tree_list是一个存储已经添加到查询集中的节点的字典,它是一种优化,算法不需要工作。但由于我正在使用一棵相对较大的树,所以我必须将它包括在内。

我想你可以把这个方法变成一个管理器,让它更通用,即让它适用于任何 MPTT 模型,而不仅仅是YourModel

于 2011-07-03T21:09:28.290 回答
4

怎么样:

def qs_ancestors(queryset):
    if isinstance(queryset, EmptyQuerySet):
        return queryset
    new_queryset = queryset.none()
    for obj in queryset:
        new_queryset = new_queryset | obj.get_ancestors()
return new_queryset

它仍然是 len(queryset) 子句。您可以通过执行预处理传递来潜在地减少子句的数量,该传递删除作为查询集中其他对象的祖先的对象,例如:

min_obj_set = []
for obj in queryset.order_by('tree_id', '-level'):
    for obj2 in min_obj_set:
        if obj.is_ancestor_of(obj2):
            break
    else:
        min_obj_set.append(obj)

尽管上面的代码片段只是一个示例,但如果您的查询集包含大量对象,您可能希望使用 BST。

不过,您必须测试与更大的数据库查询相比,这是否会提高速度。

于 2011-06-30T16:02:36.533 回答