我有一个拥有近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_one
和bulk
操作将额外的两个字段添加到集合中,我得到:
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'}
...
答案 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" }
>