将具有大量行的新字段添加到Mongodb中的现有集合

时间:2018-02-05 18:08:31

标签: mongodb pymongo

我有一个拥有近100万个文档的现有集合,现在我想在此集合中添加一个新的字段数据。 (我正在使用PyMongo)

例如,我现有的集合db.actions如下所示:

...
{'_id':12345, 'A': 'apple', 'B': 'milk'}
{'_id':12346, 'A': 'pear', 'B': 'juice'}
...

现在我想将一个新的列字段数据附加到此现有集合中:

...
{'_id':12345, 'C': 'beef'}
{'_id':12346, 'C': 'chicken'}
...

这样生成的集合应如下所示:

...
{'_id':12345, 'A': 'apple', 'B': 'milk', 'C': 'beef'}
{'_id':12346, 'A': 'pear', 'B': 'juice', 'C': 'chicken'}
...

我知道我们可以使用带有for循环的update_one执行此操作,例如

for doc in values:
        collection.update_one({'_id': doc['_id']},
        {'$set': {k: doc[k] for k in fields}},
        upsert=True
    )

其中values是一个字典列表,每个字典包含两个项目,_id键值对和新的字段键值对。 fields包含我要添加的所有新字段。

然而,问题是我有数百万个文档要更新,任何for循环的东西都太慢了,有没有办法更快地追加这个新字段?类似于insert_many的东西,除了它附加到现有的集合?

===============================================

UPDATE1:

所以这就是我现在所拥有的,

bulk = self.get_collection().initialize_unordered_bulk_op()
for doc in values:
    bulk.find({'_id': doc['_id']}).update_one({'$set': {k: doc[k] for k in fields} })

bulk.execute()

我首先使用insert_many将数据帧示例写入数据库,性能如下: Time spent in insert_many: total: 0.0457min 然后我使用update_onebulk操作将额外的两个字段添加到集合中,我得到: Time spent: for loop: 0.0283min | execute: 0.0713min | total: 0.0996min

UPDATE2:

我为现有集合和新列数据添加了一个额外的列,以便使用左连接来解决此问题。如果您使用左连接,则可以忽略_id字段。

例如,我现有的集合db.actions如下所示:

...
{'A': 'apple', 'B': 'milk', 'dateTime': '2017-10-12 15:20:00'}
{'A': 'pear', 'B': 'juice', 'dateTime': '2017-12-15 06:10:50'}
{'A': 'orange', 'B': 'pop', 'dateTime': '2017-12-15 16:09:10'}
...

现在我想将一个新的列字段数据附加到此现有集合中:

...
{'C': 'beef', 'dateTime': '2017-10-12 09:08:20'}
{'C': 'chicken', 'dateTime': '2017-12-15 22:40:00'}
...

这样生成的集合应如下所示:

...
{'A': 'apple', 'B': 'milk', 'C': 'beef', 'dateTime': '2017-10-12'}
{'A': 'pear', 'B': 'juice', 'C': 'chicken', 'dateTime': '2017-12-15'}
{'A': 'orange', 'B': 'pop', 'C': 'chicken', 'dateTime': '2017-12-15'}
...

2 个答案:

答案 0 :(得分:0)

如果您的更新对于每个文档而言确实是唯一的,则没有比bulk write API更快的速度。 MongoDB和驱动程序都无法猜出您想要更新的内容,因此您需要循环更新定义,然后批量批量更改,这里有很多描述:

Bulk update in Pymongo using multiple ObjectId

“无序”批量写入可能稍快一些(虽然在我的测试中它们不是)但我仍然主要为错误处理原因投票选择有序方法。

但是,如果您可以将更改分组为特定的重复模式,那么您最好定义一组更新查询(实际上是字典中每个唯一值的一次更新),然后发布每个更新目标的文档。我的Python在这一点上太穷了,不能为你编写整个代码,但这里是我的意思的伪代码示例:

假设您有以下更新词典:

{
    key: "doc1",
    value:
    [
        { "field1", "value1" },
        { "field2", "value2" },
    ]
}, {
    key: "doc2",
    value:
    [
        // same fields again as for "doc1"
        { "field1", "value1" },
        { "field2", "value2" },
    ]
}, {
    key: "doc3",
    value:
    [
        { "someotherfield", "someothervalue" },
    ]
}
然后,不是单独更新这三个文档,而是发送一个更新来更新前两个文档(因为它们需要相同的更改),然后更新一个更新“doc3”。您对更新模式结构的预知知识越多,即使通过对字段子集的更新进行分组,您也可以进行优化,但在某些时候可能会有点复杂......

<强>更新

根据您的以下要求,让我们试一试。

fields = ['C']
values = [
    {'_id': 'doc1a', 'C': 'v1'},
    {'_id': 'doc1b', 'C': 'v1'},
    {'_id': 'doc2a', 'C': 'v2'},
    {'_id': 'doc2b', 'C': 'v2'}
]

print 'before transformation:'
for doc in values:
    print('_id ' + doc['_id'])
    for k in fields:
        print(doc[k])

transposed_values = {}
for doc in values:
    transposed_values[doc['C']] = transposed_values.get(doc['C'], [])
    transposed_values[doc['C']].append(doc['_id'])

print 'after transformation:'
for k, v in transposed_values.iteritems():
    print k, v

for k, v in transposed_values.iteritems():
    collection.update_many({'_id': { '$in': v}}, {'$set': {'C': k}})

答案 1 :(得分:0)

由于您的联合收藏集包含较少的文档,因此您可以将dateTime转换为日期

db.new.find().forEach(function(d){
    d.date = d.dateTime.substring(0,10);
    db.new.update({_id : d._id}, d);
})

并根据日期(dateTime的子字符串)和_id,

进行多个字段查找

并转到新的集合(增强型)

db.old.aggregate(
    [
        {$lookup: {
                from : "new",
                let : {id : "$_id", date : {$substr : ["$dateTime", 0, 10]}},
                pipeline : [
                    {$match : {
                        $expr : {
                            $and : [
                                {$eq : ["$$id", "$_id"]},
                                {$eq : ["$$date", "$date"]}
                            ]
                        }
                    }},
                    {$project : {_id : 0, C : "$C"}}
                ],
                as : "newFields"
            }
        },
        {$project : {
            _id : 1,
            A : 1,
            B : 1,
            C : {$arrayElemAt : ["$newFields.C", 0]},
            date : {$substr : ["$dateTime", 0, 10]}
        }},
        {$out : "enhanced"}
    ]
).pretty()

结果

> db.enhanced.find()
{ "_id" : 12345, "A" : "apple", "B" : "milk", "C" : "beef", "date" : "2017-10-12" }
{ "_id" : 12346, "A" : "pear", "B" : "juice", "C" : "chicken", "date" : "2017-12-15" }
{ "_id" : 12347, "A" : "orange", "B" : "pop", "date" : "2017-12-15" }
>