【问题标题】:How can I remove a duplicate object from a MongoDB array?如何从 MongoDB 数组中删除重复的对象?
【发布时间】:2014-08-21 01:38:55
【问题描述】:

我的数据如下所示:

{

    "foo_list": [
      {
        "id": "98aa4987-d812-4aba-ac20-92d1079f87b2",
        "name": "Foo 1",
        "slug": "foo-1"
      },
      {
        "id": "98aa4987-d812-4aba-ac20-92d1079f87b2",
        "name": "Foo 1",
        "slug": "foo-1"
      },
      {
        "id": "157569ec-abab-4bfb-b732-55e9c8f4a57d",
        "name": "Foo 3",
        "slug": "foo-3"
      }
    ]
}

其中foo_list 是模型中名为Bar 的字段。请注意,数组中的第一个和第二个对象是完全重复的。

除了切换到 PostgresSQL 的明显解决方案之外,我还可以运行什么 MongoDB 查询来删除 foo_list 中的重复条目?

不太合适的类似答案:

这些问题回答了数组中是否包含裸字符串的问题。但是在我的情况下,数组中充满了对象。

我希望很清楚我对查询数据库不感兴趣;我希望永远从数据库中删除重复项。

【问题讨论】:

    标签: javascript mongodb mongodb-query aggregation-framework


    【解决方案1】:

    纯粹从聚合框架的角度来看,有几种方法可以解决这个问题。

    您可以在现代版本中应用 $setUnion

     db.collection.aggregate([
         { "$project": { 
             "foo_list": { "$setUnion": [ "$foo_list", "$foo_list" ] }
         }}
     ])
    

    或者更传统的是$unwind$addToSet

    db.collection.aggregate([
        { "$unwind": "$foo_list" },
        { "$group": {
            "_id": "$_id",
            "foo_list": { "$addToSet": "$foo_list" }
        }}
    ])
    

    或者,如果您只是对重复项感兴趣,那么请通过一般分组:

    db.collection.aggregate([
        { "$unwind": "$foo_list" },
        { "$group": {
            "_id": {
                "_id": "$_id",
                "foo_list": "$foo_list"
            },
            "count": { "$sum": 1 }
        }},
        { "$match": { "count": { "$ne": 1 } } },
        { "$group": {
            "_id": "$_id._id",
            "foo_list": { "$push": "$_id.foo_list" }
        }}
    ])    
    

    如果您确实想使用另一个更新语句从数据中“删除”重复项,则最后一个表单可能对您有用,因为它标识了重复的元素。

    因此,在最后一种形式中,您的样本数据返回的结果标识了重复项:

    {
        "_id" : ObjectId("53f5f7314ffa9b02cf01c076"),
        "foo_list" : [
                {
                        "id" : "98aa4987-d812-4aba-ac20-92d1079f87b2",
                        "name" : "Foo 1",
                        "slug" : "foo-1"
                }
        ]
    }
    

    从您的集合中返回每个文档的结果,其中包含数组中的重复条目以及重复的条目。这是您需要更新的信息,您可以根据需要循环结果,从结果中指定更新信息以删除重复项。

    这实际上是通过每个文档 两个 更新语句完成的,因为一个简单的 $pull 操作会删除“两个”项,这不是您想要的:

    var cursor = db.collection.aggregate([
        { "$unwind": "$foo_list" },
        { "$group": {
            "_id": {
                "_id": "$_id",
                "foo_list": "$foo_list"
            },
            "count": { "$sum": 1 }
        }},
        { "$match": { "count": { "$ne": 1 } } },
        { "$group": {
            "_id": "$_id._id",
            "foo_list": { "$push": "$_id.foo_list" }
        }}
    ])    
    
    var batch = db.collection.initializeOrderedBulkOp();
    var count = 0;
    
    cursor.forEach(function(doc) {
        doc.foo_list.forEach(function(dup) {
            batch.find({ "_id": doc._id, "foo_list": { "$elemMatch": dup } }).updateOne({
                "$unset": { "foo_list.$": "" }
            });
            batch.find({ "_id": doc._id }).updateOne({ 
                "$pull": { "foo_list": null }
            });
        ]);
    
        count++;
        if ( count % 500 == 0 ) {
            batch.execute();
            batch = db.collection.initializeOrderedBulkOp();
        }
    });
    
    if ( count % 500 != 0 )
        batch.execute();
    

    这是现代 MongoDB 2.6 及更高版本的方法,使用来自聚合的游标结果和用于更新的批量操作。但原则保持不变:

    1. 识别文档中的重复项

    2. 循环结果以发布受影响文档的更新

    3. 使用$unsetpositional $ 运算符将“第一个”匹配的数组元素设置为null

    4. 使用$pull 从数组中删除null 条目

    所以在处理完上述操作后,您的示例现在看起来像这样:

    {
        "_id" : ObjectId("53f5f7314ffa9b02cf01c076"),
        "foo_list" : [
                {
                        "id" : "98aa4987-d812-4aba-ac20-92d1079f87b2",
                        "name" : "Foo 1",
                        "slug" : "foo-1"
                },
                {
                        "id" : "157569ec-abab-4bfb-b732-55e9c8f4a57d",
                        "name" : "Foo 3",
                        "slug" : "foo-3"
                }
        ]
    }
    

    重复项被删除,“重复”项仍然完好无损。这就是您从集合中识别和删除重复数据的过程。

    【讨论】:

    • 第二个代码清单与我作为无效答案示例给出的两个答案相同,因为这是一个对象数组,而不是字符串数组。
    • @andrewrk MongoDB 不在乎。它只是将它们视为“事物”。当然,如图所示的代码已经过测试并且可以工作。我总是(除非在某个地方跑掉,但这里不是这种情况)在提交回复之前进行测试。
    • @andrewrk 也许如果您认为这行不通,或者在不行的地方尝试了其他方法,那么您的对象实际上并不是您所展示的真实复制品。如果只有“id”之类的东西是“重复的”,那么数据不是真正的“集合”,您必须以不同的方式处理“重复数据删除”。但这不是您提出问题的方式。
    • 您似乎在说第二个清单实际上并未修改数据库,而只是返回没有重复的查询结果。奇怪,因为我链接的其他问题声称第二个代码列表实际上会将删除保存到数据库中。我将尝试第三个代码清单,看看会发生什么。
    • 第三个代码清单是最接近有用答案的东西,因为我的目标不是进行查询,而是实际修改数据库并使重复项永远消失。我试过了,我没有从查询中得到任何结果。我并不是说查询不会删除重复项;我的意思是它实际上没有返回任何结果。
    猜你喜欢
    • 2020-12-13
    • 2022-12-05
    • 2013-03-16
    • 1970-01-01
    • 2018-08-15
    • 1970-01-01
    相关资源
    最近更新 更多