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

使用MongoDB $graphLookup查询telegramId=200的子节点失败求助

问题描述

我有一个User的Mongoose模型,定义如下:

const schema = new mongoose.Schema(
  {
    telegramId_parent: {
      type: Number,
      default: null,
    },
    telegramId: {
      type: Number,
      required: true,
      unique: true,
    },
    telegramName: {
      type: String,
    },
   
  }
);

数据库中的数据为:

[
  {
    _id: new ObjectId('63e8b3951e2a'),
    telegramId: 111,
    telegramName: 'ParentTest',
    telegramId_parent: null,
  },
  {
    _id: new ObjectId('63e8fa887473'),
    telegramId: 100,
    telegramName: 'child1',
    telegramId_parent: 200,
  },
  {
    _id: new ObjectId('45a2ec90e'),
    telegramId: 101,
    telegramName: 'child2',
    telegramId_parent: 200,
  },
  {
    _id: new ObjectId('63c92e'),
    telegramId: 102,
    telegramName: 'child1-1',
    telegramId_parent: 100,
  },
  {
    _id: new ObjectId('63e96'),
    telegramId: 200,
    telegramName: 'Name1',
    telegramId_parent: 111,
  },
];

我想要获取telegramId=200、深度为3的所有子节点,预期结果如下:

{
  _id: new ObjectId('63e96'),
  telegramId: 200,
  telegramName: 'Name1',
  telegramId_parent: 111,
  childs:[
      {
          _id: new ObjectId('63e8fa887473'),
          telegramId: 100,
          telegramName: 'child1',
          telegramId_parent: 200,
        },
        {
          _id: new ObjectId('45a2ec90e'),
          telegramId: 101,
          telegramName: 'child2',
          telegramId_parent: 200,
        },
        {
          _id: new ObjectId('63c92e'),
          telegramId: 102,
          telegramName: 'child1-1',
          telegramId_parent: 100,
        },
  ]
}

我的查询语句为:

User.aggregate([
  { $match: { telegramId: 200 } },
  {
    $graphLookup: {
      from: 'Users',
      startWith: '$telegramId',
      connectFromField: 'telegramId',
      connectToField: 'telegramId_parent',
      as: 'child',
      maxDepth: 3,
    },
  },
]);

但实际得到的结果中childs数组为空:

{
  _id: new ObjectId('63e96'),
  telegramId: 200,
  telegramName: 'Name1',
  telegramId_parent: 111,
  childs:[]
}

请问为什么查询不到预期的子节点?如何获取带层级的结果?


问题原因与解决方案

一、查询不到子节点的核心原因

你的$graphLookup参数存在两个关键问题:

  1. 集合名称不匹配:Mongoose会自动将模型名转为小写复数形式,比如User模型对应的实际集合名是users,但你写的是from: 'Users'(大写开头),导致无法找到目标集合,返回空数组。
  2. 字段命名不一致:查询中as字段定义为'child',但你预期结果的字段名是childs,后续处理时的命名差异也会导致显示异常。

二、修正查询获取平级子节点(匹配预期结果)

将from改为正确的集合名,并统一字段命名:

User.aggregate([
  { $match: { telegramId: 200 } },
  {
    $graphLookup: {
      from: 'users', // 对应Mongoose自动生成的小写复数集合名
      startWith: '$telegramId',
      connectFromField: 'telegramId', // 父节点的telegramId作为匹配基准
      connectToField: 'telegramId_parent', // 子节点的telegramId_parent需等于基准值
      as: 'childs', // 和预期结果的字段名保持一致
      maxDepth: 3,
    },
  },
]);

执行后就能得到所有深度≤3的子节点,平级放在childs数组中,完全匹配你的预期结果。

三、获取带层级的嵌套结果

如果需要子节点按层级嵌套(比如child1下包含child1-1),可以通过两种方式实现:

方式1:聚合查询内构建层级

利用depthField记录节点深度,再通过$map和$filter重组嵌套结构:

User.aggregate([
  { $match: { telegramId: 200 } },
  {
    $graphLookup: {
      from: 'users',
      startWith: '$telegramId',
      connectFromField: 'telegramId',
      connectToField: 'telegramId_parent',
      as: 'allChilds',
      maxDepth: 3,
      depthField: 'depth' // 添加深度字段用于分组
    },
  },
  {
    $addFields: {
      tree: {
        $mergeObjects: [
          '$$ROOT',
          {
            childs: {
              $filter: {
                input: '$allChilds',
                cond: { $eq: ['$$this.depth', 1] } // 筛选第一层子节点
              }
            }
          }
        ]
      }
    }
  },
  {
    $addFields: {
      'tree.childs': {
        $map: {
          input: '$tree.childs',
          as: 'child',
          in: {
            $mergeObjects: [
              '$$child',
              {
                childs: {
                  $filter: {
                    input: '$allChilds',
                    cond: { $eq: ['$$this.telegramId_parent', '$$child.telegramId'] }
                  }
                }
              }
            ]
          }
        }
      }
    }
  },
  { $replaceRoot: { newRoot: '$tree' } },
  { $project: { allChilds: 0 } } // 移除临时平级字段
]);

返回结果会是嵌套层级结构:

{
  _id: new ObjectId('63e96'),
  telegramId: 200,
  telegramName: 'Name1',
  telegramId_parent: 111,
  childs: [
    {
      _id: new ObjectId('63e8fa887473'),
      telegramId: 100,
      telegramName: 'child1',
      telegramId_parent: 200,
      depth: 1,
      childs: [
        {
          _id: new ObjectId('63c92e'),
          telegramId: 102,
          telegramName: 'child1-1',
          telegramId_parent: 100,
          depth: 2
        }
      ]
    },
    {
      _id: new ObjectId('45a2ec90e'),
      telegramId: 101,
      telegramName: 'child2',
      telegramId_parent: 200,
      depth: 1,
      childs: []
    }
  ]
}

方式2:代码层面递归构建层级

如果聚合查询逻辑过于复杂,可以先获取平级子节点,再用递归函数构建嵌套结构:

// 第一步:查询父节点及所有平级子节点
const [parent] = await User.aggregate([
  { $match: { telegramId: 200 } },
  {
    $graphLookup: {
      from: 'users',
      startWith: '$telegramId',
      connectFromField: 'telegramId',
      connectToField: 'telegramId_parent',
      as: 'allChilds',
      maxDepth: 3,
    },
  },
]);

// 第二步:递归构建嵌套树结构
function buildTree(nodes, parentId) {
  return nodes
    .filter(node => node.telegramId_parent === parentId)
    .map(node => ({
      ...node,
      childs: buildTree(nodes, node.telegramId)
    }));
}

parent.childs = buildTree(parent.allChilds, parent.telegramId);
delete parent.allChilds;

console.log(parent);

这种方式逻辑更直观,适合层级关系复杂的场景。


内容的提问来源于stack exchange,提问作者ogggy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 14:06:56