即使添加了唯一键,MongoDB也会复制文档

时间:2015-03-16 08:14:55

标签: php mongodb mongodb-query aggregation-framework

我创建了一个集合并添加了一个像这样的唯一键

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 的文档,然后插入。

我想要什么

  1. On Mongo Shell:在uid和sid上添加唯一键,没有具有相同uid和sid的重复文档。
  2. 在PHP方面:有类似mysql “insert(value)on duplicate key update rate = rate + 1”。那就是每当我尝试插入一个文档时,如果不存在它应该插入它应该更新文档的速率字段

3 个答案:

答案 0 :(得分:34)

恭喜,您似乎找到了一个错误。这只发生在我的测试中的MongoDB 3.0.0中,或者至少在MongoDB 2.6.6中不存在。错误现在记录在SERVER-17599

  

注意:   实际上并不是“问题”,而是“按设计”确认。删除了3.0.0版的选项。仍列在documentation中。

问题是当您尝试在“复合键”字段上具有现有重复项的集合上创建索引时,未创建索引并出现错误。在上面,索引创建应该在shell中产生:

{
    "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 )
);

不允许任何更新操作“访问与”更新“文档部分中的另一个更新操作中使用的相同路径。

答案 1 :(得分:13)

我觉得当前最流行的答案对于这样一个基本的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 }});
});

答案 2 :(得分:0)

另一种避免使用多个值重复记录的简单方法

示例: 使用以下代码,可以避免“学生姓名”和“父母姓名”字段的重复值

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

在这种情况下,我们将检查是否存在具有以下字段的文档(如果存在),如果不存在则不将数据输入到DB中。