MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

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

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

REMARQUE :Pas réellement un "problème" mais confirmé "par conception". Suppression de l'option pour la version 3.0.0. Toujours listé dans la documentation cependant.

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

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

Lorsqu'il n'y a pas de doublons, vous pouvez créer l'index comme vous essayez actuellement et il sera créé.

Donc, 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, d'autres insertions contenant des données en double ne seront pas insérées et l'erreur appropriée sera enregistrée.

La note finale 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 le .update() méthode. 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 aussi $setOnInsert pour un moyen de créer uniquement certaines données lorsque le document est réellement inséré et non lorsqu'il est modifié.

Pour votre tentative spécifique, la syntaxe correcte de .update() est 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 celui utilisé dans une autre opération de mise à jour dans cette section de document "mise à jour".