You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB中移除addToSet导致的数组重复对象(保留最新条目)

清理MongoDB数组中字段顺序不同的重复对象(保留最新条目)

问题背景

使用addToSet更新集合时,MongoDB会将字段顺序不同但内容完全一致的文档判定为唯一对象,导致数组中出现重复条目。需要清理这些重复项,仅保留每个重复组里的最后(最新)一条,且不能通过全删重加的方式破坏关联关系。

示例重复文档片段:

{
  "_id":"doc_123456789",
  "relations": [
    {
      "groupId": "59fab2dd-a3eb-4bb0-8fb5-1a2c4d0209c7",
      "basis": "HOME",
      "memberRole": "OWNER"
    },
    {
      "groupId": "ccd882a9-c3e1-4ba9-bdd7-13928fd9dd56",
      "basis": "FAMILY",
      "memberRole": "PARENT"
    },
    {
      "basis": "HOME",
      "groupId": "59fab2dd-a3eb-4bb0-8fb5-1a2c4d0209c7",
      "memberRole": "OWNER"
    },
    {
      "groupId": "634012ca-5d91-4691-ae1b-1890613cd5e2",
      "basis": "HOME",
      "memberRole": "OWNER"
    }
  ]
}

解决方案1:使用更新管道(MongoDB 4.2+)

直接在更新操作中处理数组,无需额外聚合步骤:

db.your_collection.updateMany(
  {}, // 可添加过滤条件,只处理特定文档
  [
    {
      $set: {
        relations: {
          $reduce: {
            input: { $reverseArray: "$relations" }, // 倒序遍历,优先保留最新条目
            initialValue: { uniqueMap: {}, result: [] },
            in: {
              $let: {
                vars: {
                  // 将对象转为排序键后的JSON字符串,作为唯一标识
                  uniqueKey: {
                    $function: {
                      body: function(obj) {
                        const sortedKeys = Object.keys(obj).sort();
                        return JSON.stringify(sortedKeys.reduce((acc, key) => {
                          acc[key] = obj[key];
                          return acc;
                        }, {}));
                      },
                      args: ["$$this"],
                      lang: "js"
                    }
                  }
                },
                in: {
                  uniqueMap: {
                    $mergeObjects: [
                      "$$value.uniqueMap",
                      { $arrayToObject: [[["$$uniqueKey", true]]] }
                    ]
                  },
                  result: {
                    $cond: [
                      { $in: ["$$uniqueKey", { $objectToArray: "$$value.uniqueMap" }] },
                      "$$value.result",
                      { $concatArrays: ["$$value.result", ["$$this"]] }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    // 把结果倒序还原,保持原数组的顺序结构
    { $set: { relations: { $reverseArray: "$relations.result" } } }
  ]
)

解决方案2:使用聚合+Merge(MongoDB 4.4+)

适合需要先预览处理结果再更新的场景:

db.your_collection.aggregate([
  // 展开数组并记录每个元素的原索引
  { $unwind: { path: "$relations", includeArrayIndex: "originalIndex" } },
  // 生成每个对象的唯一标识(排序键后的JSON)
  {
    $addFields: {
      uniqueKey: {
        $function: {
          body: function(obj) {
            const sortedKeys = Object.keys(obj).sort();
            return JSON.stringify(sortedKeys.reduce((acc, key) => {
              acc[key] = obj[key];
              return acc;
            }, {}));
          },
          args: ["$relations"],
          lang: "js"
        }
      }
    }
  },
  // 按文档ID和唯一标识分组,保留最后出现的元素(最新)和最大索引
  {
    $group: {
      _id: { docId: "$_id", key: "$uniqueKey" },
      latestRelation: { $last: "$relations" },
      maxIndex: { $max: "$originalIndex" }
    }
  },
  // 按原索引排序,保证数组顺序和原结构一致
  { $sort: { "_id.docId": 1, maxIndex: 1 } },
  // 重组每个文档的清理后数组
  {
    $group: {
      _id: "$_id.docId",
      cleanedRelations: { $push: "$latestRelation" }
    }
  },
  // 将结果合并回原集合
  {
    $merge: {
      into: "your_collection", // 替换为你的集合名
      on: "_id",
      whenMatched: [ { $set: { relations: "$cleanedRelations" } } ]
    }
  }
])

关键说明

  • 唯一标识生成:通过将对象的键排序后转为JSON字符串,确保字段顺序不同但内容一致的对象生成相同的唯一键,解决addToSet的判定问题。
  • 保留最新条目:倒序遍历或使用$last操作符,确保重复项中最后出现的(最新)条目被保留。
  • 兼容性:如果你的MongoDB版本低于4.4,可替换$function为$concat+固定字段顺序拼接的方式生成唯一键,例如:
    uniqueKey: { $concat: ["$relations.groupId", "-", "$relations.basis", "-", "$relations.memberRole"] }
    

内容的提问来源于stack exchange,提问作者mike

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:37:05