Django-mptt如何获取节点列表的子孙节点?

15

我正在尝试获取节点列表(一个 QuerySet)中所有节点的 descendants(include_self=True),而不仅仅是单个节点。这应该只需要一个 SQL 查询。

示例(实际上并不能正常工作):

some_nodes = Node.objects.filter( ...some_condition... ) 
some_nodes.get_descendants(include_self=True) #hopefully I would like 
to have all possible Nodes starting from every node of "some_nodes" 

目前我唯一的想法是迭代一些节点并针对每个节点运行get_descendants()函数——但这是可怕的解决方案(会产生大量 SQL 查询)。

如果没有干净的方法可以通过Django ORM来解决,那么你能否提供一个自定义的SQL查询语句呢?在此假设我有一个Node的主键列表。

编辑:如果可能的话——所有的“some_nodes”都位于同一父目录下,并且在树中处于相同的“层级”。

3个回答

11

10

感谢Craig de Stigter在django-mptt-dev组回答我的问题,如果有人需要,我会友好地转发他的解决方案,原帖链接:http://groups.google.com/group/django-mptt-dev/browse_thread/thread/637c8b2fe816304d

   from django.db.models import Q 
   import operator 
   def get_queryset_descendants(nodes, include_self=False): 
       if not nodes: 
           return Node.tree.none() 
       filters = [] 
       for n in nodes: 
           lft, rght = n.lft, n.rght 
           if include_self: 
               lft -=1 
               rght += 1 
           filters.append(Q(tree_id=n.tree_id, lft__gt=lft, rght__lt=rght)) 
       q = reduce(operator.or_, filters) 
       return Node.tree.filter(q) 

示例节点树:

T1 
---T1.1 
---T1.2 
T2 
T3 
---T3.3 
------T3.3.3 

使用示例:

   >> some_nodes = [<Node: T1>, <Node: T2>, <Node: T3>]  # QureySet
   >> print get_queryset_descendants(some_nodes)
   [<Node: T1.1>, <Node: T1.2>, <Node: T3.3>, <Node: T3.3.3>] 
   >> print get_queryset_descendants(some_nodes, include_self=True)
   [<Node: T1>, <Node: T1.1>, <Node: T1.2>, <Node: T2>, <Node: T3>, <Node: T3.3>, <Node: T3.3.3>] 

太好了!我认为应该使用Node.objects而不是Node.tree - Cory
这实际上已经内置到管理器中了。请见我的替代答案。 - Cory

1

Django mptt使用修改的先序遍历树遍历方法,如MySQL Managing Hierarchical Data文档所述。

它具有以下查询以返回某个节点下面树中的所有节点:

SELECT node.name
FROM nested_category AS node, nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
    AND parent.name = 'ELECTRONICS'
ORDER BY node.lft;

秘密在于 parent.lft 和 parent.rgt 的数字,所有子节点的 node.lft 值都会介于两者之间。
显然,该示例假定只有一个父级,并且需要使用父级名称查找父级。由于您已经拥有了父节点数据,因此可以执行以下操作:
SELECT node.id
FROM node_table
WHERE node.lft BETWEEN parent[0].lft AND parent[0].rgt
    OR node.lft BETWEEN parent[1].lft AND parent[1].rgt

如何为每个父节点生成单独的 BETWEEN 子句,就请留给您作为练习(提示:“ AND ”.join)

或者您可以在每个父节点上使用范围生成器,以获取介于每个父节点 lft 和 rgt 值之间的所有值。然后,您可以使用一个庞大的 IN 语句,而不是很多 BETWEEN 子句。

将上述任一方法与 RawQueryset 结合使用,即可获得这些模型。


哦,我突然意识到我可能会在自己的项目中使用这个方法来解决提取大量树的父节点相关问题。目前由于生成了大量的查询调用,这一过程非常缓慢。 - Chris
这并不是我所问的非常精确的答案:这个语句中没有tree_id!因此,选择左右之间的节点的想法是正确的。 - thedk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接