web-dev-qa-db-fra.com

MongoDB Duplicate Documents même après l'ajout d'une clé unique

J'ai créé une collection et ajouté une clé unique comme celle-ci

db.user_services.createIndex({"uid":1 , "sid": 1},{unique:true,dropDups: true})

La collection ressemble à ceci "user_services"

{
 "_id" : ObjectId("55068b35f791c7f81000002d"),
 "uid" : 15,
 "sid" : 1,
 "rate" : 5
},
{

 "_id" : ObjectId("55068b35f791c7f81000002f"),
 "uid" : 15,
 "sid" : 1,
 "rate" : 4
}

Problème:

J'utilise le pilote php pour insérer des documents avec le même id et sid et il est inséré.

Ce que je veux

  1. Sur Mongo Shell: Ajoutez une clé unique sur uid et sid sans documents en double avec les mêmes uid et sid.
  2. On PHP Side: avoir quelque chose comme mysql "insert (value) on duplicate key update rate = rate + 1" C'est à chaque fois que j'essaie d'insérer un document, il doit être inséré sinon il doit mettre à jour le champ de taux du document
18
Raj Sharma

Félicitations, vous semblez avoir trouvé un bug. Cela ne se produit qu'avec MongoDB 3.0.0 dans mes tests, ou du moins n'est pas présent sur MongoDB 2.6.6. Bogue maintenant enregistré à SERVER-17599

[~ # ~] note [~ # ~] : Pas vraiment un "problème" mais confirmé "par conception". Suppression de l'option pour la version 3.0.0. Toujours répertorié dans la documentation cependant.

Le problème est que l'index n'est pas en cours de création et des erreurs lorsque vous essayez de le créer sur une collection avec des doublons existants dans les champs "clé composée". Sur ce qui précède, la création d'index devrait produire ceci dans le Shell:

{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "errmsg" : "exception: E11000 duplicate key error dup key: { : 15.0, : 1.0 }",
    "code" : 11000,
    "ok" : 0
}

En l'absence de doublons, vous pouvez créer l'index tel que vous essayez actuellement et il sera créé.

Pour contourner ce problème, supprimez d'abord les doublons avec une procédure comme celle-ci:

db.events.aggregate([
    { "$group": {
        "_id": { "uid": "$uid", "sid": "$sid" },
        "dups": { "$Push": "$_id" },
        "count": { "$sum": 1 }
    }},
    { "$match": { "count": { "$gt": 1 } }}
]).forEach(function(doc) {
    doc.dups.shift();
    db.events.remove({ "_id": {"$in": doc.dups }});
});

db.events.createIndex({"uid":1 , "sid": 1},{unique:true})

Ensuite, les insertions supplémentaires contenant des données en double ne seront pas insérées et l'erreur appropriée sera enregistrée.

La dernière remarque ici est que "dropDups" n'est/n'était pas une solution très élégante pour supprimer les données en double. Vous voulez vraiment quelque chose avec plus de contrôle comme démontré ci-dessus.

Pour la deuxième partie, plutôt que d'utiliser .insert() utilisez la méthode .update(). Il a une option "" upsert "

$collection->update(
    array( "uid" => 1, "sid" => 1 ),
    array( '$set' => $someData ),
    array( 'upsert' => true )
);

Ainsi, les documents "trouvés" sont "modifiés" et les documents non trouvés sont "insérés". Voir également $setOnInsert pour un moyen de créer certaines données uniquement lorsque le document est réellement inséré et non pas lorsque modifié.


Pour votre tentative spécifique, la syntaxe correcte de .update() est de trois arguments. "requête", "mise à jour" et "options":

$collection->update(
    array( "uid" => 1, "sid" => 1 ),
    array(
        '$set' => array( "field" => "this" ),
        '$inc' => array( "counter" => 1 ),
        '$setOnInsert' => array( "newField" => "another" )
   ),
   array( "upsert" => true )
);

Aucune des opérations de mise à jour n'est autorisée à "accéder au même chemin" que celle utilisée dans une autre opération de mise à jour dans cette section de document "mise à jour".

37
Neil Lunn

J'ai l'impression que la réponse actuelle la plus populaire est un peu trop locale et détaillée pour une opération MongoDB aussi élémentaire - supprimer les doublons de mongo par une clé.

La suppression des doublons par une clé pour mongo> 3.0 est simple. Exécutez simplement cette requête, en remplaçant yourDuplicateKey et en supposant que _id est votre clé primaire (assurez-vous de mongodump juste au cas où):

db.yourCollection.aggregate([
    { "$group": {
        "_id": { "yourDuplicateKey": "$yourDuplicateKey" },
        "dups": { "$Push": "$_id" },
        "count": { "$sum": 1 }
    }},
    { "$match": { "count": { "$gt": 1 } }}
]).forEach(function(doc) {
    doc.dups.shift();
    db.yourCollection.remove({ "_id": {"$in": doc.dups }});
});
18
chakeda

Un autre moyen simple d'éviter les enregistrements en double en utilisant plusieurs valeurs

Exemple: en utilisant le code suivant, on peut éviter les valeurs en double pour les champs "Nom de l'élève" et "Nom du parent"

    $DataForDB = array( "AdmissionNo" => $admissionNo, 
    "StudentName" => $StudentName, "ParentName" => $ParentName);
    if(empty($Coll->findOne(array("StudenName" => $StudentName, "ParentName" => $ParentName)))){
    $Coll->insertOne($DataForDB);
    }

Dans ce cas, nous vérifions si le document avec les champs suivants existe ou non s'il existe, les données ne sont pas entrées dans la base de données s'il n'existe pas, les données sont entrées.

0
Tech guy