拥有多个键值的mongoDB`upsert`

时间:2015-07-13 16:20:40

标签: python mongodb mongodb-query upsert

我从Amazon Mechanical Turk中提取一些数据并将其保存在mongodb集合中。

我有多个工作人员重复每项任务,因为一点点冗余可以帮助我检查工作质量。

每次我使用boto AWS python interface从亚马逊中提取数据时,我都会获得一个包含所有已完成的HIT的文件,并希望将它们插入到集合中。

以下是我要插入document的{​​{1}}:

collection
  • mongo_doc = \ {'subj_id' :data['subj_id'], 'img_id' :trial['img_id'], 'data_list' :trial['data_list'], 'worker_id' :worker_id, 'worker_exp' :worker_exp, 'assignment_id':ass_id } 是图像数据库中图像的标识符。
  • img_id是该图片中人物的标识符(每张图片可能有多个)。
  • subj_id是我从AMT工作人员那里获得的数据。
  • data_listworker_idworker_exp是有关AMT工作人员和作业的变量。

使用boto的连续提取将包含相同的数据,但我不想在我的收藏中包含重复的文档。

我知道两种可能的解决方案,但没有一种方法适合我:

  1. 我可以在集合中搜索文档,只有在不存在时插入它。但这会产生很高的计算成本。

  2. 我可以使用upsert作为确保仅在尚未包含某个键时才插入文档的方法。但是,由于多个工作人员重复执行任务,因此可以复制所有包含的密钥。

  3. 第2部分的注意事项:   - assignment_idsubj_idimg_id可以重复,因为不同的工作人员注释相同的主题,图像并可以提供相同的数据。   - data_listworker_idworker_exp可以复制,因为工作人员在同一作业中注释多个图像。   - 唯一独特的是所有这些领域的组合。

    如果以前没有插入assignment_id,是否可以插入mongo_doc

1 个答案:

答案 0 :(得分:2)

只要你想在这里做的“全部”是“插入”项目,那么你在这里有几个选择:

  1. 在所有必填字段中创建“唯一”索引并使用insert。简单地说,当值的组合与已存在的值相同时,将抛出“重复键”错误。这会阻止同样的事情被添加两次并且可以提示异常。这可能最适合用于操作的Bulk Operations API和“无序”标志。 insert_many()可以使用相同的“无序”,但我个人更喜欢Bulk API的语法,因为它允许更好的构建和混合操作:

    bulk = pymongo.bulk.BulkOperationBuilder(collection,ordered=False)
    bulk.insert(document)
    result = bulk.execute()
    

    如果在调用.execute()之前使用了多个操作,则所有操作都会立即发送到服务器,并且只有“一个”响应。使用“无序”时,所有项目都会被处理,例如“重复”键,而“结果”包含任何失败项目的报告。

    这里显而易见的“成本”是在所有字段上创建“唯一”索引将使用相当大的空间以及为“写入”操作增加大量开销,因为必须写入索引信息以及数据。

  2. $setOnInsert使用“upsert”功能。这允许您使用“所有必需的唯一字段”构造查询,以便“搜索”文档以查看是否存在。标准的“upsert”行为位于未找到文档的位置,然后创建“新”文档。

    $setOnInsert添加的内容是,该语句中“set”的所有字段仅应用于发生“upsert”的位置。在常规“匹配”上,$setOnInsert内的所有作业都将被忽略:

    bulk = pymongo.bulk.BulkOperationBuilder(collection,ordered=True)
    bulk.find({ 
        "subj_id": data["subj_id"], 
        "img_id": data["img_id"] 
        "data_list": data["data_list"],
        "worker_id": data["worker_id"], 
        "worker_exp": data["worker_exp"], 
        "assignment_id": data["assignment_id"]
    }).upsert().update_one({
        "$setOnInsert": {
            # Just the "insert" fields or just "data" as an object for all
            "subj_id": data["subj_id"], 
            "img_id": data["img_id"] 
            "data_list": data["data_list"],
            "worker_id": data["worker_id"], 
            "worker_exp": data["worker_exp"], 
            "assignment_id": data["assignment_id"]
        },
        "$set": {
            # Any other fields "if" you want to update on match
        }
    })
    result = bulk.execute()
    

    根据您的需要,如果文档匹配,您可以使用$set或其他操作符来“更新”您要“更新”的内容,或者将其完全保留,只有“插入”才会匹配。< / p>

    不能当然要做的事情就是为1内的字段指定$setOnInsert的值,然后在其他字段上执行$inc之类的操作操作。这会产生冲突,您尝试修改“相同路径”并抛出错误。

    在这种情况下,最好将$inc字段“移出”$setOnInsert块,然后让它正常进行操作。 { "$inc": 1 }只会在第一次提交时分配1。同样适用于$push和其他运营商。

    “成本”再次作为一个指数,它不需要“独特”但可能应该是。如果没有索引,操作将“扫描集合”以获得可能的匹配,而不是更有效的索引。所以它不是“必需的”,但是在没有指定索引的情况下,额外“写入”的成本通常会超过“查找”的成本。

  3. 与“批量”操作结合使用时的另一个优点是,由于$setOnInsert的“upsert”方法在查询中包含所有唯一键时不会抛出任何“重复键”错误,因此可以使用如所示,“有序”写入批次。

    当在一批操作中“命令”时,操作将在添加它们的“序列”中处理,因此如果对您来说重要的是“第一个”插入发生的是那个被调用的那个,那么它是“无序”是一种令人厌恶的,它虽然能够更快地并行执行,但当然不能保证按照构造它们的顺序提交操作。

    无论哪种方式,您都需要使用任一表单在多个键上维护“唯一”项目。可能另一种看待“降低”索引成本的方法是,用您认为“独特”的所有值替换文档的_id字段。

    由于该主键始终是“唯一的”并且始终是“必需的”,因此最小化了编写“附加索引”的“成本”,并且可能是一个需要考虑的选项。 _id并不“需要”成为ObjectId,因为它可以是一个复合对象,那么如果你有另一个唯一标识符,那么以这种方式使用它可能是明智的,避免进一步的唯一复制。