J'ai environ 1,7 million de documents dans mongodb (à l'avenir, 10 mois et plus). Certains d'entre eux représentent une entrée en double que je ne veux pas. La structure du document ressemble à ceci:
{
_id: 14124412,
nodes: [
12345,
54321
],
name: "Some beauty"
}
Le document est dupliqué s'il a à au moins un noeud identique comme un autre document avec même nom. Quel est le moyen le plus rapide de supprimer les doublons?
En supposant que vous vouliez supprimer définitivement les documents contenant une entrée name
+ nodes
en double de la collection, vous pouvez ajouter un index unique
avec l'option dropDups: true
:
db.test.ensureIndex({name: 1, nodes: 1}, {unique: true, dropDups: true})
Comme le dit la documentation, soyez extrêmement prudent car cela effacera les données de votre base de données. Sauvegardez d'abord votre base de données au cas où elle ne fonctionnerait pas exactement comme prévu.
METTRE À JOUR
Cette solution est uniquement valide via MongoDB 2.x car l’option dropDups
n’est plus disponible dans la version 3.0 ( docs ).
L'option dropDups: true
n'est pas disponible dans la version 3.0.
J'ai la solution avec le cadre d'agrégation pour la collecte des doublons, puis la suppression en une fois.
Cela peut être un peu plus lent que les modifications "d'index" au niveau du système. Mais il est bon de penser à la manière dont vous souhaitez supprimer les documents en double.
une. Supprimer tous les documents en une fois
var duplicates = [];
db.collectionName.aggregate([
{ $match: {
name: { "$ne": '' } // discard selection criteria
}},
{ $group: {
_id: { name: "$name"}, // can be grouped on multiple properties
dups: { "$addToSet": "$_id" },
count: { "$sum": 1 }
}},
{ $match: {
count: { "$gt": 1 } // Duplicates considered as count greater than one
}}
],
{allowDiskUse: true} // For faster processing if set is larger
) // You can display result until this and check duplicates
.forEach(function(doc) {
doc.dups.shift(); // First element skipped for deleting
doc.dups.forEach( function(dupId){
duplicates.Push(dupId); // Getting all duplicate ids
}
)
})
// If you want to Check all "_id" which you are deleting else print statement not needed
printjson(duplicates);
// Remove all duplicates in one go
db.collectionName.remove({_id:{$in:duplicates}})
b. Vous pouvez supprimer des documents un à un.
db.collectionName.aggregate([
// discard selection criteria, You can remove "$match" section if you want
{ $match: {
source_references.key: { "$ne": '' }
}},
{ $group: {
_id: { source_references.key: "$source_references.key"}, // can be grouped on multiple properties
dups: { "$addToSet": "$_id" },
count: { "$sum": 1 }
}},
{ $match: {
count: { "$gt": 1 } // Duplicates considered as count greater than one
}}
],
{allowDiskUse: true} // For faster processing if set is larger
) // You can display result until this and check duplicates
.forEach(function(doc) {
doc.dups.shift(); // First element skipped for deleting
db.collectionName.remove({_id : {$in: doc.dups }}); // Delete remaining duplicates
})
Créer un dump de collection avec mongodump
Collection claire
Ajouter un index unique
Restaurer la collection avec mongorestore
J'ai trouvé cette solution qui fonctionne avec MongoDB 3.4: Je supposerai que le champ avec des doublons s'appelle fieldX
db.collection.aggregate([
{
// only match documents that have this field
// you can omit this stage if you don't have missing fieldX
$match: {"fieldX": {$nin:[null]}}
},
{
$group: { "_id": "$fieldX", "doc" : {"$first": "$$ROOT"}}
},
{
$replaceRoot: { "newRoot": "$doc"}
}
],
{allowDiskUse:true})
En tant que nouveau sur mongoDB, j'ai passé beaucoup de temps et utilisé d’autres longues solutions pour rechercher et supprimer les doublons. Cependant, je pense que cette solution est nette et facile à comprendre.
Cela fonctionne en commençant par faire correspondre les documents contenant fieldX (certains documents ne contenaient pas ce champ et j'ai obtenu un résultat vide supplémentaire).
L'étape suivante regroupe les documents par fieldX et insère uniquement le document $ first dans chaque groupe à l'aide de $$ ROOT . Enfin, il remplace l'ensemble du groupe agrégé par le document trouvé à l'aide de $ first et $$ ROOT.
J'ai dû ajouter allowDiskUse car ma collection est volumineuse.
Vous pouvez l'ajouter après n'importe quel nombre de pipelines, et bien que la documentation de $ first mentionne une étape de tri avant d'utiliser $ first, elle a fonctionné sans moi. "Impossible de poster un lien ici, ma réputation est inférieure à 10 :("
Vous pouvez enregistrer les résultats dans une nouvelle collection en ajoutant une étape $ out ...
Alternativement, si on ne s'intéresse qu'à quelques champs, par exemple. field1, field2, et non le document entier, en phase de groupe sans replaceRoot:
db.collection.aggregate([
{
// only match documents that have this field
$match: {"fieldX": {$nin:[null]}}
},
{
$group: { "_id": "$fieldX", "field1": {"$first": "$$ROOT.field1"}, "field2": { "$first": "$field2" }}
}
],
{allowDiskUse:true})
L’idée générale est d’utiliser findOne https://docs.mongodb.com/manual/reference/method/db.collection.findOne/ Pour récupérer un identifiant aléatoire parmi les enregistrements dupliqués de la collection.
Supprimez tous les enregistrements de la collection autres que l’identité aléatoire que nous avons extraite de l’option findOne.
Vous pouvez faire quelque chose comme ça si vous essayez de le faire en pymongo.
def _run_query():
try:
for record in (aggregate_based_on_field(collection)):
if not record:
continue
_logger.info("Working on Record %s", record)
try:
retain = db.collection.find_one(find_one({'fie1d1': 'x', 'field2':'y'}, {'_id': 1}))
_logger.info("_id to retain from duplicates %s", retain['_id'])
db.collection.remove({'fie1d1': 'x', 'field2':'y', '_id': {'$ne': retain['_id']}})
except Exception as ex:
_logger.error(" Error when retaining the record :%s Exception: %s", x, str(ex))
except Exception as e:
_logger.error("Mongo error when deleting duplicates %s", str(e))
def aggregate_based_on_field(collection):
return collection.aggregate([{'$group' : {'_id': "$fieldX"}}])
De la coquille:
La méthode suivante fusionne des documents portant le même nom tout en ne conservant que les nœuds uniques sans les dupliquer.
J'ai trouvé que l’utilisation de l’opérateur $out
était simple. Je déroule le tableau, puis le groupe en ajoutant à set. L'opérateur $out
permet au résultat de l'agrégation de persister [docs] . Si vous mettez le nom de la collection elle-même, elle remplacera la collection par les nouvelles données. Si le nom n'existe pas, une nouvelle collection sera créée.
J'espère que cela t'aides.
allowDiskUse
devra peut-être être ajouté au pipeline.
db.collectionName.aggregate([
{
$unwind:{path:"$nodes"},
},
{
$group:{
_id:"$name",
nodes:{
$addToSet:"$nodes"
}
},
{
$project:{
_id:0,
name:"$_id.name",
nodes:1
}
},
{
$out:"collectionNameWithoutDuplicates"
}
])