Django+PostgreSQL:高效获取递归分类结构方案咨询
问题1:高效获取整个分类层级的所有对象
你的递归SQL存在逻辑错误:parent_id存储的是父分类的主键ID(整数类型),但你用sm.parent_id = h.slug关联,这会导致匹配失败。先修正CTE查询:
WITH RECURSIVE hierarchy(id, name, slug, parent_id, level) AS ( -- 初始查询:获取所有根分类(parent_id为NULL),如果要指定某个根分类,改成WHERE id = 18000 SELECT id, name, slug, parent_id, 0 AS level FROM categories_category WHERE parent_id IS NULL UNION ALL -- 递归查询:关联子分类与父分类的ID SELECT cc.id, cc.name, cc.slug, cc.parent_id, h.level + 1 FROM categories_category cc JOIN hierarchy h ON cc.parent_id = h.id ) SELECT * FROM hierarchy ORDER BY level, name;
在Django中可以直接用原生查询执行这段SQL,一次性拉取所有层级的分类,避免N+1查询问题:
from collections import defaultdict def get_category_hierarchy(): categories = Category.objects.raw(""" WITH RECURSIVE hierarchy(id, name, slug, parent_id, level) AS ( SELECT id, name, slug, parent_id, 0 AS level FROM categories_category WHERE parent_id IS NULL UNION ALL SELECT cc.id, cc.name, cc.slug, cc.parent_id, h.level + 1 FROM categories_category cc JOIN hierarchy h ON cc.parent_id = h.id ) SELECT * FROM hierarchy ORDER BY level, name; """) # 可选:将查询结果整理为树形字典,方便后续模板渲染 category_tree = defaultdict(list) for cat in categories: category_tree[cat.parent_id].append(cat) return category_tree
如果你的分类层级频繁查询或结构复杂,推荐使用django-mptt或django-treebeard这类专门处理树形结构的库:
- 它们会在模型中添加额外字段(如左右值、层级),让树形查询的效率大幅提升
- 提供现成的API(如
get_descendants()、get_ancestors())直接获取层级关系,无需手动写CTE
问题2:模板中展示分类层级
方法1:基于原生CTE结果的递归模板渲染
假设视图中已将分类整理为category_tree字典(键为父分类ID,值为子分类列表),根分类对应键为None:
在主模板category_list.html中:
<ul class="category-tree"> {% for root_cat in category_tree.None %} <li> <a href="/categories/{{ root_cat.slug }}/">{{ root_cat.name }}</a> {% include "subcategory_list.html" with parent_id=root_cat.id %} </li> {% endfor %} </ul>
创建子模板subcategory_list.html用于递归渲染子分类:
{% if category_tree.get(parent_id) %} <ul class="subcategory-tree"> {% for sub_cat in category_tree.get(parent_id) %} <li> <a href="/categories/{{ sub_cat.slug }}/">{{ sub_cat.name }}</a> {% include "subcategory_list.html" with parent_id=sub_cat.id %} </li> {% endfor %} </ul> {% endif %}
方法2:使用django-mptt的现成模板标签
如果使用django-mptt,只需在视图中获取根分类,然后用内置标签递归渲染:
视图代码:
# 先给Category模型添加TreeManager: # class Category(models.Model): # ... # objects = TreeManager() def category_list(request): root_categories = Category.objects.root_nodes() return render(request, 'category_list.html', {'root_categories': root_categories})
模板代码:
{% load mptt_tags %} <ul class="category-tree"> {% recursetree root_categories %} <li> <a href="/categories/{{ node.slug }}/">{{ node.name }}</a> {% if not node.is_leaf_node %} <ul class="children"> {{ children }} </ul> {% endif %} </li> {% endrecursetree %} </ul>
内容的提问来源于stack exchange,提问作者OhMad
相关产品推荐
相关产品推荐

