如何通过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
相关产品推荐
相关产品推荐

