如何使用 MongoDB 上的嵌套数据进行双重 $lookup 聚合?

How to make double $lookup aggregation with nested data on MongoDB?

我有 3 个模型:

  1. 学习
  2. 字集
  3. 类别

Study model引用了WordSet,然后WordSet引用了Category。

我了解到,为了正常显示数据,我使用填充。 但是在这种情况下,我需要一个包含很多 $lookup.

的查询

如何从 WordSet 'populate' 类别中只显示重复次数最多的类别?

我会得到这样的回应:

"stats": [
    {
        "_id": null,
        "numberOfStudies": 4,
        "averageStudyTime": 82.5,
        "allStudyTime": 330,
        "longestStudy": 120,
        "allLearnedWords": 8
        "hardestCategory": "Work" // only this field is missing
    }
]

我试过这样做:

   const stats = await Study.aggregate([
  {
    // join User table 
    $lookup: {
      from: 'User',
      let: { userId: '$user' },
      pipeline: [
        {
          $match: { $expr: { $eq: ['$_id', '$$userId'] } },
        },
      ],
      as: 'currentUser',
    },
  },
  {
   // join WordSet table
    $lookup: {
      from: 'WordSet',
      let: { wordSetId: '$learnedWordSet' },
      pipeline: [
        {
          $match: { $expr: { $eq: ['$_id', '$$wordSetId'] } },
        },
        {
         // from this moment i'm not sure how to make it work
          $lookup: {
            from: 'Category',
            let: { categoryId: '$category' },
            pipeline: [
              {
                $match: { $expr: { $in: ['$_id', '$$categoryId'] } },
              },
            ],
            as: 'category',
          },
        },
      ],
      as: 'wordSet',
    },
  },
  { // add wordset with category? this is not working
    $addFields: {
      wordSet: {
        $arrayElemAt: ['$wordSet', 0],
      },
    },
  },
  { // search by logged user
    $match: { user: new ObjectID(currentUserId) },
  },
  { 
    $group: {
      // display statistics about user's studying
      _id: null,
      numberOfStudies: { $sum: 1 },
      averageStudyTime: { $avg: '$studyTime' },
      allStudyTime: { $sum: '$studyTime' },
      longestStudy: { $max: '$studyTime' },
      allLearnedWords: { $sum: { $size: '$learnedWords' } },
      // category: check which category is repeated the most and display it
    },
  },
]);

学习

     const studySchema = new mongoose.Schema({
  name: {
    type: String,
  },
  studyTime: {
    type: Number,
  },
  learnedWords: [String],
  notLearnedWords: [String],
  learnedWordSet: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'WordSet',
  },
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
  },
});

字集

const wordSetSchema = new mongoose.Schema({
      name: {
        type: String,
      },
      category: {
        type: [
          {
            type: mongoose.Schema.Types.ObjectId,
            ref: 'Category',
            required: true,
          },
        ],
      },
    });

类别

const categorySchema = new mongoose.Schema({
  name: {
    type: String,
  },
});

不知道我理解的对不对,你可以试试查询,我已经改进了stages的使用,

  • $match总是尝试在第一阶段使用阶段
  • $lookup with User collection, there is no need to pipeline version, you can use localField and foreignField properties

I don't think is there any use of user document, and lookup stage because you want only statistics as per last $group stage. so you can skip this lookup stage

  • 在 WordSet 查找中,
    • $match你的情况
    • $project 显示必填字段
    • $unwind解构category数组
    • $group乘以category得到总计数
    • $sortcount 降序排列
    • $limit 仅获取最常用的第一个和单个元素
    • $llokupCategory 集合
    • $project 显示必填字段,获取第一个类别名称
  • $group阶段,hardestCategory获取$first类别名称
const stats = await Study.aggregate([
  { $match: { user: new ObjectID(currentUserId) } },
  {
    $lookup: {
      from: "User",
      localField: "user",
      foreignField: "_id",
      as: "currentUser"
    }
  },
  {
    $lookup: {
      from: "WordSet",
      let: { wordSetId: "$learnedWordSet" },
      pipeline: [
        { $match: { $expr: { $eq: ["$_id", "$$wordSetId"] } } },
        {
          $project: {
            _id: 0,
            category: 1
          }
        },
        { $unwind: "$category" },
        {
          $group: {
            _id: "$category",
            count: { $sum: 1 }
          }
        },
        { $sort: { count: -1 } },
        { $limit: 1 },
        {
          $lookup: {
            from: "Category",
            localField: "_id",
            foreignField: "_id",
            as: "category"
          }
        },
        {
          $project: {
            _id: 0,
            category: { $arrayElemAt: ["$category.name", 0] }
          }
        }
      ],
      as: "wordSet"
    }
  },
  {
    $group: {
      _id: null,
      numberOfStudies: { $sum: 1 },
      averageStudyTime: { $avg: "$studyTime" },
      allStudyTime: { $sum: "$studyTime" },
      longestStudy: { $max: "$studyTime" },
      allLearnedWords: {
        $sum: { $size: "$learnedWords" }
      },
      hardestCategory: {
        $first: {
          $first: "$wordSet.category"
        }
      }
    }
  }
])

Playground