MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

MongoDB Duplica i documenti anche dopo aver aggiunto una chiave univoca

Congratulazioni, sembra che tu abbia trovato un bug. Questo accade solo con MongoDB 3.0.0 nei miei test, o almeno non è presente in MongoDB 2.6.6. Bug ora registrato su SERVER-17599

NOTA :Non in realtà un "problema" ma confermato "in base alla progettazione". Eliminata l'opzione per la versione 3.0.0. Tuttavia è ancora elencato nella documentazione.

Il problema è che l'indice non viene creato ed errori quando si tenta di crearlo su una raccolta con duplicati esistenti nei campi "chiave composta". Su quanto sopra, la creazione dell'indice dovrebbe produrre questo nella shell:

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

Quando non sono presenti duplicati puoi creare l'indice come stai attualmente provando e verrà creato.

Quindi, per aggirare il problema, rimuovi prima i duplicati con una procedura come questa:

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

Quindi non verranno inseriti ulteriori inserimenti contenenti dati duplicati e verrà registrato l'errore appropriato.

La nota finale qui è che "dropDups" è/non era una soluzione molto elegante per rimuovere i dati duplicati. Vuoi davvero qualcosa con più controllo, come dimostrato sopra.

Per la seconda parte, invece di usare .insert() usa il .update() metodo. Ha un'opzione "upsert"

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

Quindi i documenti "trovati" vengono "modificati" e i documenti non trovati vengono "inseriti". Vedi anche $setOnInsert per un modo per creare determinati dati solo quando il documento è effettivamente inserito e non quando modificato.

Per il tuo tentativo specifico, la sintassi corretta di .update() sono tre argomenti. "interrogazione", "aggiornamento" e "opzioni":

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

Nessuna delle operazioni di aggiornamento può "accedere allo stesso percorso" utilizzato in un'altra operazione di aggiornamento in quella sezione del documento "aggiornamento".