如何更新MongoDB中的字段,该字段汇总子文档的值?

如何更新MongoDB中的字段,该字段汇总子文档的值?,mongodb,pymongo,Mongodb,Pymongo,我有一份文件的结构如下: { 'item_id': '12345' 'total_score': 100, 'user_scores': { 'ABC': 40, 'DEF': 60 } } 我使用的是PyMongo,但MongoDB的文档似乎很容易跨不同的发行版进行翻译。使用PyMongo,我可以通过以下方式更新用户分数: collection.update_one( { 'item_id': '12345' },

我有一份文件的结构如下:

{
    'item_id': '12345'
    'total_score': 100,
    'user_scores': {
        'ABC': 40,
        'DEF': 60
    }
}
我使用的是PyMongo,但MongoDB的文档似乎很容易跨不同的发行版进行翻译。使用PyMongo,我可以通过以下方式更新用户分数:

collection.update_one(
    { 'item_id': '12345' },
    { '$set': { 'user_scores.GHI': 20 } },
    upsert=True
)
其结果是:

{
    'item_id': '12345'
    'total_score': 100,
    'user_scores': {
        'ABC': 40,
        'DEF': 60,
        'GHI': 20
    }
}
问题当然是
总分现在不正确。我想更新总分,以便在将来的查询中,我可以快速确定每个结果的分数,甚至可以按分数排序

一种解决方案是使用
find_One({'item_id:'12345'})
(如果不存在则创建),然后使用新分数更新现有文档,并更新总分。这里的问题是,我想同时运行数千个这样的程序,对一系列请求调用
bulk\u write
效率要高得多

因此,更好的解决方案是执行两个顺序更新请求:

request1 = UpdateOne(
    { 'item_id' : '12345' },
        { '$set': { 'user_scores.GHI': 20 } },
        upsert = True
)

request2 = UpdateOne(
    { 'item_id' : '12345' },
        { '$set': { 'total_score': { '$sum': { '$values': 'user_scores' } } } },
        upsert = True
)
第一个请求更新用户分数,与之前相同。第二个要求,有两个概念。这句话的语法不正确,但我想做的是:

  • 我需要从
    用户评分
    字典中获取值<代码>{'$values':'user_scores'}
  • 就是我试图传达这一点的方式
  • 这给了我一个值数组。我知道这些都是数字,所以我现在需要求和,用
    {'$sum':{'$values':'user_scores'}
    表示

  • 我可以连续运行这些批处理更新,因此不会出现求和错误的风险。拥有
    总分
    字段的危险在于该字段始终未更新,因此不包含正确的数字。我认为这是基于文档的模型的常见情况?

    如果您使用的是Mongo 4.2+版,他们引入了一个新功能:,这意味着现在您可以一次性完成您想要的任务:

    db.collection.updateOne({ 'item_id' : '12345' },
        [
            { '$set': { 'user_scores.GHI': 20 } },
            { '$set': { 'total_score': { '$sum': [ "$user_scores.GHI", "$user_scores.ABC", "$user_scores.GHI"] } } },,
        ]);
    
    不幸的是,对于较小的Mongo版本,这是不可能的,因此如果是这种情况,您将不得不继续使用您的解决方案,将其拆分为两个操作

    编辑: 对于动态更新,我们可以这样使用:


    好极了我在4.2.3中使用Mongo Cloud,所以这将非常好。我剩下的问题是关于设置为$sum的语法。1.在第二行$set中,出现一个错误
    “total_score.$sum'中以美元($)为前缀的字段'$sum'对于存储无效。”
    2。我仍然需要单独查询并知道存在哪些用户分数,才能写出这一行。有没有一种方法可以让我基本上说“对于所有用户的分数值”?是的,我们可以,我在我的答案中添加了一个编辑。借助聚合表达式的强大功能,它非常简单!
    db.collection.updateOne(
        {'item_id': '12345'},
        [
            {'$set': {'user_scores.GHI': 20}},
            {
                '$set':
                    {
                        'total_score': {
                            '$sum': {
                                '$map': {
                                    'input': {'$objectToArray': '$user_scores'},
                                    'as': 'score',
                                    'in': '$$score.v'
                                }
                            }
                        }
                    }
            }
        ]);