如何使用Mongoose获取数组中各ID对应的最后一条文档?
Absolutely, you can pull this off entirely with Mongoose (thanks to MongoDB's aggregation framework)—no manual post-processing of results needed. Here's how to fetch the latest QuizResult document for each levelAnswered ID in your levelIds array:
Step-by-Step Aggregation Pipeline
We'll use a series of aggregation stages to filter, sort, group, and reshape your data to get exactly what you need:
QuizResult.aggregate([ // 1. Narrow down to only the relevant documents for the user and target levels { $match: { levelAnswered: { $in: levelIds }, answeredByUser: result.applicant._id } }, // 2. Sort documents so the newest entry for each level comes first { $sort: { created: -1 } }, // 3. Group by level ID, keeping only the latest document in each group { $group: { _id: "$levelAnswered", latestResult: { $first: "$$ROOT" } // $$ROOT refers to the entire document object } }, // 4. Optional: Reshape output to match the structure of a regular find() result { $replaceRoot: { newRoot: "$latestResult" } } ]) .then(latestResults => { // latestResults is an array of the most recent QuizResult per levelId console.log(latestResults); }) .catch(err => { console.error("Error fetching latest results:", err); });
Breakdown of Each Stage:
- $match: Filters out irrelevant documents upfront, reducing the data we process in later stages.
- $sort: Orders matched documents by
createddate in descending order, ensuring the newest entry for each level is at the top of its group. - $group: Clusters documents by
levelAnsweredID. Using$first: "$$ROOT"grabs the very first (and thus latest) document from each sorted group. - $replaceRoot: Optional but convenient—this moves the nested
latestResultobject up to the root of each result entry, making the output look like what you'd get from a standardfind()query.
Handling Populated Fields
If you need to resolve the referenced fields (like answeredByUser or levelAnswered), you have two straightforward options:
Option 1: Use Mongoose's populate After Aggregation
Aggregation results don't auto-populate, but you can run populate on the returned array:
QuizResult.aggregate([/* ... pipeline as above ... */]) .then(results => { return QuizResult.populate(results, { path: 'levelAnswered answeredByUser' }); }) .then(populatedResults => { // Now populatedResults includes the full referenced documents });
Option 2: Add $lookup to the Aggregation Pipeline
You can directly join with referenced collections in the pipeline using MongoDB's $lookup stage. For example, to populate levelAnswered:
QuizResult.aggregate([ // ... match, sort, group, replaceRoot stages ... { $lookup: { from: "quizlevels", // Name of your QuizLevel collection (Mongoose pluralizes model names by default) localField: "levelAnswered", foreignField: "_id", as: "levelAnswered" } }, { $unwind: "$levelAnswered" } // Convert the array from $lookup to a single object ]) // ... rest of your code
Why This Is Better Than Multiple Queries
Using aggregation is way more efficient than running a separate findOne for each levelId (which would trigger N database calls for N IDs). The pipeline handles everything in one round trip, cutting down on network overhead and improving performance.
内容的提问来源于stack exchange,提问作者claudiomatiasrg

