【问题标题】:mongoDB `upsert` with multiple key values具有多个键值的 mongoDB `upsert`
【发布时间】:2015-10-02 00:27:26
【问题描述】:

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

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

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

这是我要插入collectiondocument

    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 是该图像中人物的标识符(每张图像可能有多个)。
  • data_list 是我从 AMT 工作人员那里获得的数据。
  • worker_idworker_expassignment_id 是关于 AMT 工作器和分配的变量。

使用boto 的连续拉取将包含相同的数据,但我不想在我的集合中有重复的文档。

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

  1. 我可以在集合中搜索文档并仅在不存在时插入它。但这会产生非常高的计算成本。

  2. 我可以使用upsert 来确保仅当某个键尚未包含时才插入文档。但是所有包含的键都可以复制,因为该任务由多个工作人员重复。

关于第 2 部分的注意事项: - subj_idimg_iddata_list 可以重复,因为不同的工作人员注释相同的主题、图像并且可以提供相同的数据。 - worker_idworker_expassignment_id可以重复,因为工作人员在同一任务中注释了多个图像。 - 唯一独特的是所有这些字段的组合。

有没有一种方法可以插入 mongo_doc,前提是它之前没有插入?

【问题讨论】:

  • 是的。$setOnInsert
  • @BlakesSeven - 非常感谢!你能说明一下典型用法吗?

标签: python mongodb mongodb-query upsert


【解决方案1】:

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

  1. 在所有必填字段中创建一个“唯一”索引并使用插入。简单地说,当值的组合与已经存在的东西相同时,将抛出“重复键”错误。这会阻止相同的东西被添加两次,并且可以提醒您异常。这可能最好与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 或其他运算符来处理您“想要”在文档匹配时更新的任何内容,或者将其完全省略,仅在不匹配的地方出现“插入”。

    不能做的当然是对$setOnInsert中的一个字段赋值1,然后在其他操作上做$inc之类的事情。这会在您尝试修改“相同路径”时产生冲突并引发错误。

    在这种情况下,最好将$inc 字段“移出”$setOnInsert 块,让它正常运行。 { "$inc": 1 } 无论如何都会在第一次提交时分配 1。同样适用于$push 和其他运算符。

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

与“批量”操作结合使用的另一个优势是,由于带有 $setOnInsert 的“upsert”方法在查询中所有唯一键都存在时不会抛出任何“重复键”错误,因此可以将其与“有序”一起使用" 如图所示为批处理写入。

当在一批操作中“排序”时,这些操作会按照它们添加的“顺序”进行处理,所以如果对您来说重要的是发生的“第一个”插入是已提交的插入,那么它就是优先于“无序”,虽然可以更快地并行执行,但当然不能保证按照它们的构造顺序提交操作。

无论哪种方式,您都需要为具有任一形式的多个键维护“唯一”项目的成本。要“降低”索引成本,另一种方法可能是查看将文档的 _id 字段替换为您认为“唯一”的所有值。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多