2016-07-25 23 views
0

Ich habe eine Sammlung, wo es einige doppelte Dokumente gibt. Im Beispiel:Duplikate zusammenführen und die ältesten entfernen

Erstes Dokument:

{ 
    "_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" 
} 

Zweites Dokument:

{ 
    "_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 Feld enthält gleiche sequense von Zahlen in beiden Dokumenten 84358.

So würde Ich mag diese Schritte erreichen:

  1. Schleife über jedes Dokument in der Sammlung.
  2. die Zahlenfolge in jedem Dokument im Link Feld (d.h. 84358 oben) passen, und wenn es mehrere Dokumente in Sammlung, die diese Sequenz in dem Link Bereich hat. Und auch wenn Place Feld Spiel in beiden Dokumenten:
  3. Merge PresentInDB und Checked Felder -> fusioniert PresentInDB und Checked Felder durch Hinzufügen von Array-Werten aus dem neuesten Dokument (nach Datum in AddedDate Feld) mit dem ältesten Dokument.
  4. Entfernen Sie das neueste Dokument.

Wie könnte ich eine solche Abfrage erreichen?

Antwort

1

In MongoDB 3.3.6 Release wird ein eingeführt $split Operator für den Umgang mit Strings im Aggregation Framework (Jira). Vor dieser Version konnte man dies nur mit einer map/reduce Lösung lösen.

Nach MongoDB 3.3.6 Release: Aggregation Rahmen Lösung

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" 
    } 
    ] 
); 

Vor MongoDB 3.3.6 Release: Map/Reduce Lösung

Karte Funktion:

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

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

    emit(keyDoc, this); 
}; 

Reduce Funktion:

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; 
}; 

Karte/Verkleinerung:

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

Abwickelwerkzeug der Wert aus der Map/Reduce zurück Dokumente:

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

Großartig, vielen Dank! – user1665355

0

können Sie eine einzelne aggregation Abfrage verwenden, das zu tun:

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 Array auf sie
  • $match Ihre ID (hier enthält 84.358)
  • $sort nach aufsteigend Datum zu arbeiten
  • $group mit:
    • ein $addToSet auf alle Ihre PresentInDB in einem einzigen Array zu verschmelzen ohne Duplikate
    • ein $first für jedes Feld zu halten. das erste Haltemittel nur Sie die ältere wollen, da wir nach aufsteigend Datum sortiert zuvor
  • $out werden die Ergebnisse in eine neue Kollektion documents hier genannt speichern
+0

Danke, aber ich habe die Frage zu aktualisieren, scheint, wie das Update nicht vor jetzt der Fall war ... Ich habe zwei Felder füge "PresentInDB" und "Checked" Felder ein. Wie könnte ich das mit Aggregat machen? – user1665355

+0

Ich muss auch das neueste doppelte Dokument aus dieser Sammlung "DROP", nicht neue Sammlung hinzufügen! – user1665355

+0

Und auch, es gibt mehrere Duplikate, nicht nur '" Link ":/84358 /' – user1665355