如何在MongoDB中更新符合特定规则的重复文档?
Got it, let's tackle this problem step by step. You need to flag older duplicate documents (matching ipAddr and bccId, with active: true but different sessionId) by setting their active field to false. Here's how to do it properly with MongoDB's aggregation and update operations:
Step 1: Identify Duplicate Documents to Update
First, use an aggregation pipeline to pinpoint which documents need their active field flipped. This pipeline will:
- Filter for documents where
activeis alreadytrue - Group them by
ipAddrandbccIdto find duplicates - Sort each group by
lastUpdated(newest first) - Exclude the newest document in each group (since we want to keep it active)
db.mycollection.aggregate([ // Filter only active documents { $match: { active: true } }, // Group by ipAddr + bccId, collect all docs in each group { $group: { _id: { ipAddr: "$ipAddr", bccId: "$bccId" }, docs: { $push: { _id: "$_id", sessionId: "$sessionId", lastUpdated: "$lastUpdated" } }, totalDocs: { $sum: 1 } } }, // Keep only groups with duplicates (more than 1 document) { $match: { totalDocs: { $gt: 1 } } }, // Sort each group's docs by lastUpdated (newest first) { $addFields: { sortedDocs: { $sortArray: { input: "$docs", sortBy: { lastUpdated: -1, _id: -1 } } } } }, // Skip the newest document, keep the rest to update { $addFields: { docsToDeactivate: { $slice: ["$sortedDocs", 1, { $size: "$sortedDocs" }] } } }, // Unwind the list of docs to update into individual documents { $unwind: "$docsToDeactivate" }, // Keep only the _id of documents to update { $replaceRoot: { newRoot: { _id: "$docsToDeactivate._id" } } } ])
Note: The
_id: -1in the sort step handles edge cases where two documents have the exact samelastUpdatedvalue—it ensures we only keep one document active (the one with the larger_id).
Step 2: Batch Update the Identified Documents
Once you have the list of documents to deactivate, you can use updateMany to set their active field to false. Here's how to combine the aggregation and update in one workflow:
Option 1: Two-Step Approach (Works in All MongoDB Versions)
// First, fetch all _ids of documents to deactivate const idsToDeactivate = db.mycollection.aggregate([ // Paste the aggregation pipeline from Step 1 here ]).map(doc => doc._id); // Batch update the documents db.mycollection.updateMany( { _id: { $in: idsToDeactivate } }, { $set: { active: false } } );
Option 2: Single-Step with $merge (MongoDB 4.2+)
For a more efficient, single-step operation, use MongoDB's $merge stage to write the update directly from the aggregation:
db.mycollection.aggregate([ // Paste the aggregation pipeline from Step 1 here (up to $replaceRoot) { $merge: { into: "mycollection", on: "_id", whenMatched: { $set: { active: false } }, whenNotMatched: "discard" // Ignore any unexpected matches } } ]);
Verification
After running the update, you can verify the result with this query to ensure only the newest document per ipAddr/bccId pair remains active:
db.mycollection.aggregate([ { $match: { active: true } }, { $group: { _id: { ipAddr: "$ipAddr", bccId: "$bccId" }, count: { $sum: 1 } } }, { $match: { count: { $gt: 1 } } } ])
If this returns no results, your operation worked correctly!
内容的提问来源于stack exchange,提问作者Igor

