合并重复项并删除最旧的

时间:2016-07-25 18:37:25

标签: mongodb merge mapreduce updates

我有一个有一些重复文件的集合。例如:

第一份文件:

{
    "_id" : ObjectId("56f3d7cc1de31cb20c08ae6b"),
    "AddedDate" : ISODate("2016-05-01T00:00:00.000Z"),
    "Place": "THISPLACE",
    "PresentInDB" : [ 
        {
            "InDB" : ISODate("2016-05-01T00:00:00.000Z")
        }
     ],
    "Checked" : [],
    "Link": "http://www.mylink.com/first/84358"
}

第二份文件:

{
    "_id" : ObjectId("577740526c1e542904725238"),
    "AddedDate" : ISODate("2016-05-02T00:00:00.000Z"),
    "Place": "THISPLACE",
    "PresentInDB" : [ 
        {
            "InDB" : ISODate("2016-05-02T00:00:00.000Z")
        },
        {
            "InDB" : ISODate("2016-05-03T00:00:00.000Z")
        }
     ],
    "Checked" : [
        {
            "Done" : ISODate("2016-05-02T00:00:00.000Z")
        },
    ],
    "Link": "http://www.mylink.com/second/84358"
}

Link字段在两个文档84358中包含相同的数字序列。

所以我想实现这些步骤:

  1. 遍历集合中的每个文档。
  2. 匹配Link字段中每个文档中的数字序列(即上面的84358)以及是否有多个文档 在Link字段中具有该序列的集合。如果Place字段在两个文档中匹配:
  3. 合并PresentInDBChecked字段 - >通过添加最新文档中的数组值来合并PresentInDBChecked字段(按AddedDate中的日期 字段)到最旧的文件。
  4. 删除最新文档。
  5. 我怎样才能实现这样的查询?

2 个答案:

答案 0 :(得分:2)

在MongoDB 3.3.6版本中引入了一个$split运算符,用于处理聚合框架中的字符串(Jira)。在此版本之前,您只能使用map / reduce解决方案解决此问题。

MongoDB 3.3.6发布后:聚合框架解决方案

db.duplicatedCollection.aggregate(
  [
    {
      $project: {
        _id : 1,
        AddedDate : 1,
        Place : 1,
        PresentInDB : 1,
        Checked : 1,
        Link : 1,
        sequenceNumber: { $arrayElemAt: [ {$split: ["$Link", "/"]}, -1 ]},
      }
    }, 
    {
      $sort: { AddedDate: 1 }
    },
    {
      $group: {
        _id : {
          sequenceNumber : "$sequenceNumber",
          Place : "$Place"
        },
        id : { $first: "$_id"},
        AddedDate: { $first: "$AddedDate" },
        Place :  { $first: "$Place" },
        PresentInDB: {
          $push: '$PresentInDB'
        },
        Checked: {
          $push: '$Checked'
        },
        Link: { $first: "$Link"}
      }
    }, 
   {
      $unwind: "$PresentInDB"
    },
    {
      $unwind: {
        path : "$PresentInDB",
        preserveNullAndEmptyArrays: true
      }    
    },
    {
      $unwind: "$Checked"
    },
    {
      $unwind: {
        path : "$Checked",
        preserveNullAndEmptyArrays: true
      }
    },    
    {
      $group: {
        _id : "$id",
        AddedDate: { $first: "$AddedDate" },        
        Place :  { $first: "$Place" },
        PresentInDB : {
          $addToSet: '$PresentInDB'
        },
        Checked : {
          $addToSet: '$Checked'
        },        
        Link: { $first: "$Link"}
      }
    },  
    {
      $out: "duplicatedCollection"
    }
  ]
);

在MongoDB 3.3.6发布之前:Map / Reduce解决方案

地图功能:

var mapFunction = function() {
    var linkArray = this.Link.split("/");
    var sequenceNumber = linkArray[linkArray.length - 1];

    var keyDoc = { 
       place : this.Place,
       sequenceNumber: sequenceNumber, 
    };

    emit(keyDoc, this);
};

减少功能:

var reduceFunction = function(key, values) {   
    var reducedDoc = {};
    reducedDoc._id = values[0]._id;
    reducedDoc.AddedDate = values[0].AddedDate;
    reducedDoc.Link = values[0].Link;
    reducedDoc.PresentInDB = [];
    reducedDoc.Checked = [];

    var presentInDbMillisArray = [];
    var checkedMillisArray = [];        

    values.forEach(function(doc) {
        if (reducedDoc.AddedDate < doc.AddedDate) {
            reducedDoc._id = doc._id;
            reducedDoc.AddedDate = doc.AddedDate;
            reducedDoc.Link = doc.Link;
        } 

        // PresentInDB field merge
        doc.PresentInDB.forEach(function(presentInDBElem) {
            var millis = presentInDBElem.InDB.getTime();
            if (!Array.contains(presentInDbMillisArray, millis)) { 
                reducedDoc.PresentInDB.push(presentInDBElem);
                presentInDbMillisArray.push(millis);
            }
        });

        // same here with Checked field
        doc.Checked.forEach(function(checkedElem) {
            var millis = checkedElem.Done.getTime();
            if (!Array.contains(checkedMillisArray, millis)) { 
                reducedDoc.Checked.push(checkedElem);
                checkedMillisArray.push(millis);
            }
        });
    });
    return reducedDoc;
};

地图/减少:

db.duplicatedCollection.mapReduce(
    mapFunction,
    reduceFunction,
    { 
        "out": "duplicatedCollection"
    }
);

从map / reduce返回的文档中解开值:

   db.duplicatedCollection.find(
    {
        value : {
            $exists: true 
        }
    }
    ).forEach(function(doc) {
        db.duplicatedCollection.insert(doc.value);
        db.duplicatedCollection.remove({_id : doc._id});
    });

答案 1 :(得分:0)

您可以使用单个aggregation查询来执行此操作:

SimpleGridServer
  • db.device.aggregate([{ "$unwind": "$PresentInDB" }, { "$match": { "Link": /84358/ } }, { "$sort": { "AddedDate": 1 } }, { "$group": { _id: 0, PresentInDB: { $addToSet: '$PresentInDB' }, AddedDate: { $first: "$AddedDate" }, id: { $first: "$_id" }, Link: { $first: "$Link" } } }, { $out: "documents" }]) 您的阵列正在处理它
  • $unwind您的ID(此处包含84358)
  • $match按升序日期
  • $sort
    • $group将您的所有$addToSet合并为一个没有重复的数组
    • 要保留的每个字段PresentInDB。保留第一个意味着您只需要旧版本,因为我们之前按升序日期排序
  • $first会将结果保存到名为$out此处
  • 的新集合中