You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 23:40:26