MongoDB 在同一集合子文档中使用aggregate与$lookup的技术问询
Hey there! Let's walk through how to use MongoDB's aggregate with $lookup to populate subdocuments in your users collection, based on the sample data you shared.
Your collection has user documents with a follows array, where each entry contains a userId string pointing to another user in the same collection. We'll use aggregation to replace those userIds with full user details.
Method 1: Unwind & Group (Most Compatible)
This approach works across most MongoDB versions and explicitly handles each entry in the follows array:
db.users.aggregate([ // 1. Save the original document ID to rebuild the array later { $addFields: { originalId: "$_id" } }, // 2. Split the follows array into individual documents { $unwind: "$follows" }, // 3. Convert string userId to ObjectId (critical for matching _id!) { $addFields: { "follows.userId": { $toObjectId: "$follows.userId" } } }, // 4. Look up the full user document for each userId { $lookup: { from: "users", localField: "follows.userId", foreignField: "_id", as: "follows.userDetails" } }, // 5. Flatten the userDetails array (lookup always returns an array) { $unwind: "$follows.userDetails" }, // 6. Group entries back into the original user document { $group: { _id: "$originalId", name: { $first: "$name" }, email: { $first: "$email" }, password: { $first: "$password" }, // Omit this in production! follows: { $push: "$follows" } } } ])
Breakdown of Each Stage:
$addFields: Preserves the original document ID so we can reassemble thefollowsarray later.$unwind: Splits thefollowsarray into separate documents, making it easy to run$lookupon each entry.$addFields(conversion): Fixes the type mismatch between your stringuserIdand the target user'sObjectId_id.$lookup: Pulls the full user data from the sameuserscollection, storing it infollows.userDetails.$unwind(second): Converts the single-elementuserDetailsarray into a flat object.$group: Recombines all entries back into the original user document, rebuilding the populatedfollowsarray.
Method 2: Pipeline-Based Lookup (MongoDB 3.6+)
If you prefer avoiding $unwind, use a nested pipeline in $lookup to process the array directly:
db.users.aggregate([ { $lookup: { from: "users", let: { followIds: { $map: { input: "$follows", as: "f", in: { $toObjectId: "$$f.userId" } } } }, pipeline: [ { $match: { $expr: { $in: ["$_id", "$$followIds"] } } }, { $project: { name: 1, email: 1, _id: 1 } // Exclude sensitive fields like password } ], as: "followsPopulated" } } ])
This creates a new followsPopulated array with full user documents for every followed user, without modifying the original follows array.
Key Notes:
- Sensitive Data: Never return fields like
passwordin production queries—adjust the$groupor$projectstages to exclude them. - Type Consistency: Always ensure
userIdmatches the type of_id(either both strings or bothObjectIds) to avoid lookup failures.
内容的提问来源于stack exchange,提问作者Laurent Chriqui

