Подтвердить что ты не робот

MongoDB Duplicate Documents даже после добавления уникального ключа

Я создал коллекцию и добавил уникальный ключ, подобный этому

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

Коллекция выглядит примерно так: "user_services"

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

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

Проблема:

Я использую php-драйвер для вставки документов с такими же uid и sid, и он встает.

Что я хочу

  • В Mongo Shell: добавьте уникальный ключ на uid и sid без дубликатов документов с такими же uid и sid.
  • На стороне PHP: иметь что-то вроде mysql "insert (value) при удвоенной скорости обновления ключа = rate + 1". Это когда я пытаюсь вставить документ, он должен быть вставлен, если нет, то он должен обновить поле скорости документа
4b9b3361

Ответ 1

Поздравляем, вы обнаружили ошибку. Это происходит только с MongoDB 3.0.0 в моем тестировании или, по крайней мере, нет в MongoDB 2.6.6. Ошибка теперь записана на SERVER-17599

Примечание: На самом деле "проблема", но подтверждена "по дизайну". Выбросил вариант для версии 3.0.0. Тем не менее, он включен в .

Проблема заключается в том, что индекс не создается и ошибки при попытке создать его в коллекции с существующими дубликатами в полях "составной ключ". В этом случае создание индекса должно дать это в оболочке:

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

Если нет дубликатов, вы можете создать индекс, как вы сейчас пытаетесь, и он будет создан.

Итак, чтобы обойти это, сначала удалите дубликаты с помощью следующей процедуры:

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

Затем дополнительные вставки, содержащие повторяющиеся данные, не будут вставлены и будет записана соответствующая ошибка.

Наконец, обратите внимание, что "dropDups" является/не очень элегантным решением для удаления повторяющихся данных. Вы действительно хотите что-то с большим контролем, как показано выше.

Для второй части вместо использования .insert() используйте метод .update(). У этого есть опция "upsert"

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

Таким образом, "найденные" документы "изменены", а не найденные документы "вставлены". Также см. $setOnInsert, чтобы создать только определенные данные, когда документ фактически вставлен, а не когда он изменен.


Для вашей конкретной попытки правильный синтаксис .update() - это три аргумента. "запрос", "обновление" и "параметры":

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

Ни одна из операций обновления не разрешает "доступ к одному и тому же пути", как используется в другой операции обновления в этом разделе "Обновление".

Ответ 2

Я чувствую, что текущий самый популярный ответ немного слишком локальный и подробный для такой элементарной операции MongoDB - удаление дубликатов из mongo с помощью ключа.

Удаление дубликатов ключом для mongo > 3.0 прост. Просто запустите этот запрос, заменив yourDuplicateKey и предположив, что _id - ваш первичный ключ (убедитесь, что вы mongodump на всякий случай):

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