MongoDB 3.4:如何将聚合结果检查、执行聚合并保存、返回结果合并为单条聚合管道?
First, a critical note about your current use of $out: in MongoDB 3.4, the $out stage replaces the entire target collection each time it runs. That means if you run this aggregation for multiple users, you'll lose all previous results in outCollection every time you process a new user. That's probably not what you want, so let's fix that first.
Now, to address your main question: combining the existence check, aggregation execution/save, and result return into a single operation. Unfortunately, you can't do this in a single aggregation pipeline in MongoDB 3.4—here's why:
- Aggregation pipelines in 3.4 are primarily read-only, except for
$out, which is an all-or-nothing replacement of the target collection (not suitable for your use case of storing multiple user results). - There's no way to conditionally execute aggregation steps or perform upsert writes within the same pipeline based on an existence check.
But don't worry—we can combine these steps into a single logical operation using a script (e.g., in the MongoDB shell or via your driver) that handles all three steps efficiently. Here's how:
Step-by-Step Workaround
- Check for existing results: First, query
outCollectionto see if the document for youruser_idexists. - Return existing results if found: If the document exists, immediately return its
aggResult. - Run aggregation and upsert if not found: If no existing document exists, run your aggregation pipeline (without the
$outstage), then upsert the result intooutCollection, and return the computed result.
Example MongoDB Shell Script
function getAggResult(userId) { // Step 1: Check for existing result const existing = db.outCollection.findOne({ user_id: userId }); if (existing) { // Step 2: Return existing result return existing.aggResult; } // Step 3: Run aggregation without $out const aggResult = db.sourceCollection.aggregate([ { $match: { user_id: userId } }, // ... your other aggregation steps here ], { allowDiskUse: true }).toArray(); // Upsert the result into outCollection db.outCollection.updateOne( { user_id: userId }, { $set: { aggResult: aggResult } }, { upsert: true } ); // Return the computed result return aggResult; } // Usage: getAggResult("myUserId");
Key Notes for This Approach
- Avoids overwriting the collection: Using
updateOnewithupsert: trueensures we only add/update the specific user's document, preserving other users' results. - Minimizes redundant work: We only run the expensive aggregation if the result doesn't already exist.
- Atomicity considerations: While this isn't a fully atomic operation (there's a small window where another process could insert the same user's document after our check but before our upsert), in most cases this is acceptable. If you need strict atomicity, you'd need to upgrade to MongoDB 4.0+ to use transactions, but that's a bigger leap.
What If You Had a Newer MongoDB Version?
Just for context, if you were on MongoDB 4.2+, you could use the $merge stage (instead of $out) to upsert documents, and combine it with a $lookup to check existence. But since you're on 3.4, the script above is the most practical solution.
内容的提问来源于stack exchange,提问作者uylmz

