向MongoDB中的现有集合添加具有大量行的新字段

Posted

技术标签:

【中文标题】向MongoDB中的现有集合添加具有大量行的新字段【英文标题】:Add a new field with large number of rows to existing collection in Mongodb 【发布时间】:2018-07-15 16:14:53 【问题描述】:

我有一个现有的集合,其中包含近 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'
...

我知道我们可以用 update_one 和 for 循环来做到这一点,例如

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 的东西,除了它附加到现有集合?

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

更新1:

这就是我现在所拥有的,

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

更新2:

我在现有集合和新列数据中都添加了一个额外的列,目的是使用左连接来解决这个问题。如果您使用左连接,则可以忽略 _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'
...

【问题讨论】:

db.collection.initializeUnorderedBulkOp()docs.mongodb.com/manual/reference/method/… @Saravana 为此,我认为我仍然需要执行 for 循环来创建 bulk.updates,然后运行 ​​execute。我已经比较了使用bulkexecuteinsert,但它仍然比insert_many慢得多,我希望得到类似insert_many的性能。 您不能简单地为具有 $addfields 阶段的聚合创建一个管道阶段,然后使用类似db.mydb.values.aggregate(pipeline) 的东西运行它吗? @MichaëlvanderHaven 我会尝试研究一下(我对 MongoDB 有点陌生,仍然熟悉它的命令等), 用于在聚合框架中使用 python 调用设置管道:api.mongodb.com/python/current/examples/aggregation.html 对于$addfields 阶段,请看这里:docs.mongodb.com/manual/reference/operator/aggregation/… 【参考方案1】:

如果每个文档的更新确实是独一无二的,那么没有什么比 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)

【讨论】:

我明白了,很高兴知道。我试图更新的新字段的值并不完全是唯一的。例如字段名称是相同的,只是所有的“C”,并且值只有某些类别,即“v1”、“v2”和“v3”就是这样。你能在 mongo 命令中显示一个伪代码吗?看来我不必在您发布的链接中拆分批量操作。 @Sam:查看更新后的答案。我不确定我的数据结构是否与您的数据结构匹配,但它应该为您指明正确的方向。【参考方案2】:

由于您的联接集合的文档较少,您可以将 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" 
> 

【讨论】:

我收到此错误"errmsg" : "arguments to $lookup must be strings,但经过快速搜索后,这似乎是由 v3.4 和 v3.6 中的 $lookup 的 different sytanx 引起的,然后,安装在 v。 3.6好像在win7上卡住了,我先试试dnickless的解决方案。

以上是关于向MongoDB中的现有集合添加具有大量行的新字段的主要内容,如果未能解决你的问题,请参考以下文章

使用 mongoose 通过 update 方法向 mongodb 中的现有文档添加新字段

使用 mongoose 通过 update 方法向 mongodb 中的现有文档添加新字段

MongoDB:如何为集合中的每个文档设置一个等于另一个字段值的新字段[重复]

如何将具有值的新列添加到现有数据表?

如何将 MongoDB 集合中的 _id 字段更改为 User_id?

在 MongoDB 中使用集合的新字段更新索引可能会产生啥结果?