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

如何通过MongoDB聚合构建动态嵌套分类树形结构?

MongoDB聚合生成嵌套树形分类结构方案

你可以通过$graphLookup配合自定义逻辑实现需求的嵌套树形结构,以下是具体实现方式:

方法一:MongoDB 5.0+ 原生聚合实现

利用$graphLookup获取所有后代节点,再通过$function将扁平的后代数组转换为嵌套的children结构:

db.category.aggregate([
  // 筛选根分类(parentId 为 null)
  { $match: { parentId: null } },
  // 获取当前根分类的所有后代节点,按层级升序排列
  {
    $graphLookup: {
      from: "category",
      startWith: "$_id",
      connectFromField: "_id",
      connectToField: "parentId",
      as: "descendants",
      sortBy: { level: 1 }
    }
  },
  // 将扁平的后代数组转换为嵌套树形结构
  {
    $addFields: {
      children: {
        $function: {
          body: function(descendants) {
            const nodeMap = {};
            // 初始化每个节点的children数组,并建立ID到节点的映射
            descendants.forEach(node => {
              node.children = [];
              nodeMap[node._id] = node;
            });
            const rootChildren = [];
            // 遍历节点,将子节点挂载到对应父节点的children中
            descendants.forEach(node => {
              if (nodeMap[node.parentId]) {
                nodeMap[node.parentId].children.push(node);
              } else {
                rootChildren.push(node);
              }
            });
            return rootChildren;
          },
          args: ["$descendants"],
          lang: "js"
        }
      }
    }
  },
  // 移除临时的descendants字段
  { $project: { descendants: 0 } }
])

关键说明

  • $graphLookup会批量获取每个根节点的所有后代,返回的descendants是扁平数组
  • 自定义函数通过构建节点映射表,快速将子节点挂载到父节点的children数组,最终形成嵌套结构
  • 为parentId字段创建索引可大幅提升$graphLookup的查询性能

方法二:低版本MongoDB(4.x及以下)兼容方案

如果你的MongoDB版本不支持$function,可以先通过聚合查询出根节点及对应扁平后代,再在应用层处理成树形结构(以Node.js为例):

聚合查询部分

const rootCategories = await db.category.aggregate([
  { $match: { parentId: null } },
  {
    $graphLookup: {
      from: "category",
      startWith: "$_id",
      connectFromField: "_id",
      connectToField: "parentId",
      as: "descendants",
      sortBy: { level: 1 }
    }
  }
]).toArray();

应用层树形转换

function buildNestedTree(descendants) {
  const nodeMap = {};
  descendants.forEach(node => {
    node.children = [];
    nodeMap[node._id] = node;
  });
  const tree = [];
  descendants.forEach(node => {
    const parentNode = nodeMap[node.parentId];
    if (parentNode) {
      parentNode.children.push(node);
    } else {
      tree.push(node);
    }
  });
  return tree;
}

// 生成最终树形结构
const finalResult = rootCategories.map(cat => ({
  ...cat,
  children: buildNestedTree(cat.descendants),
  descendants: undefined // 移除临时字段
}));

替代方案:固定层级的多次$lookup(不推荐)

如果你的分类层级是固定且较少的(比如最多4层),可以用多次$lookup手动关联子节点,但扩展性差,层级变动时需要修改聚合管道:

db.category.aggregate([
  { $match: { parentId: null } },
  // 关联二级分类
  {
    $lookup: {
      from: "category",
      localField: "_id",
      foreignField: "parentId",
      as: "children"
    }
  },
  // 关联三级分类
  {
    $unwind: { path: "$children", preserveNullAndEmptyArrays: true }
  },
  {
    $lookup: {
      from: "category",
      localField: "children._id",
      foreignField: "parentId",
      as: "children.children"
    }
  },
  // 重新分组恢复根节点结构
  {
    $group: {
      _id: "$_id",
      name: { $first: "$name" },
      parentId: { $first: "$parentId" },
      level: { $first: "$level" },
      slug: { $first: "$slug" },
      children: { $push: "$children" }
    }
  },
  // 过滤空的children项
  {
    $addFields: {
      children: {
        $filter: {
          input: "$children",
          cond: { $ne: ["$$this", null] }
        }
      }
    }
  }
])

内容的提问来源于stack exchange,提问作者Mesut Çiftçi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:23:13