使用MongoDB中的聚合值更新多个文档

时间:2016-10-30 02:08:00

标签: mongodb aggregation-framework updates document

问题1

我有一个名为recipe的集合,其中所有文档都有一个数组字段ingredients。我想计算这些数组项并将它们写入新字段ingredient_count

问题2

还有一个名为ingredient的集合。文档有一个count字段,它是所有食谱中的总使用次数。

我当前的方法

我现在的解决方案是一个聚集整个集合并逐个更新所有文档的脚本:

// PROBLEM 1: update recipe documents
db.recipe.aggregate(
    [
        {
            $project: {
                numberOfIngredients: { $size: "$ingredients" }
            }
        }
    ]
).forEach(function(recipe) {
    db.recipe.updateOne(
        { _id: recipe._id },
        { $set: { incredient_count: recipe.numberOfIngredients } }
    )
});

// PROBLEM 2: update ingredient documents
db.ingredient.find().snapshot().forEach(function(ingredient) {
    db.ingredient.updateOne(
        { _id: ingredient._id },
        { $set: { count: db.recipe.count({ ingredients: { $in: [ingredient.name] } })) } }
    )
});

非常慢。知道如何更有效地做到这一点吗?

1 个答案:

答案 0 :(得分:2)

对于这两个问题,只能执行输出到新集合的聚合,这些集合将替换现有集合:

问题1

聚合包含一个$project,用于计算成分,并保留字段列表:

db.recipe.aggregate([{
    $project: {
        ingredients: 1,
        numberOfIngredients: { $size: "$ingredients" }
    }
}, {
    $out: "recipeNew"
}])

给你:

{ "_id" : ObjectId("58155bc09c924e717c5c4240"), "ingredients" : [......], "numberOfIngredients" : 5 }
{ "_id" : ObjectId("58155bc19c924e717c5c4241"), "ingredients" : [......], "numberOfIngredients" : 3 }

聚合的结果将写入可以替换现有recipeNew集合的新集合recipe

Problem2

聚合包含:

  • 1 $unwind删除成分阵列
  • 1 $group来总结每种成分的发生情况。按成分分组_id
  • 1 $lookup将成分集合加入当前聚合以检索指定成分的所有字段
  • 1 $unwind删除导入成分项目数组
  • 1 $project选择要保留的字段
  • 1 $out将结果输出到新集合

查询是:

db.recipe.aggregate([{
    $unwind: "$ingredients"
}, {
    $group: { _id: "$ingredients", IngredientsNumber: { $sum: 1 } }
}, {
    $lookup: {
        from: "ingredients",
        localField: "_id",
        foreignField: "_id",
        as: "ingredientsDB"
    }
}, {
    $unwind: { path: "$ingredientsDB", preserveNullAndEmptyArrays: true }
}, {
    $project: {
        ingredientsNumber: "$IngredientsNumber",
        name: "$ingredientsDB.name"
    }
}, {
    $out: "ingredientsTemp"
}])

这给出了:

{ "_id" : ObjectId("5812caaeb4829937f4599b54"), "ingredientsNumber" : 2, "name" : "ingredients5" }
{ "_id" : ObjectId("5812caaeb4829937f4599b53"), "ingredientsNumber" : 1, "name" : "ingredients4" }
{ "_id" : ObjectId("5812caaeb4829937f4599b52"), "ingredientsNumber" : 2, "name" : "ingredients3" }
{ "_id" : ObjectId("5812caaeb4829937f4599b51"), "ingredientsNumber" : 1, "name" : "ingredients2" }
{ "_id" : ObjectId("5812caaeb4829937f4599b50"), "ingredientsNumber" : 2, "name" : "ingredients1" }

此解决方案的缺点:

  • 它使用$project,因此您需要指定要保留的字段
  • 您将获得一个新的ingredientsTemp集合,其中仅包含实际存在于食谱中的成分,因此需要使用$lookup进行一次额外聚合,以将现有的聚合与您从该聚合中获得的聚合一起加入:

以下内容将加入现有的ingredients集合与我们创建的集合:

db.ingredients.aggregate([{
    $lookup: {
        from: "ingredientsTemp",
        localField: "_id",
        foreignField: "_id",
        as: "ingredientsDB"
    }
}, {
    $unwind: { path: "$ingredientsDB", preserveNullAndEmptyArrays: true }
}, {
    $project: {
        name: "$name",
        ingredientsNumber: "$ingredientsDB.ingredientsNumber"
    }
}])

然后你会:

{ "_id" : ObjectId("5812caaeb4829937f4599b50"), "name" : "ingredients1", "ingredientsNumber" : 2 }
{ "_id" : ObjectId("5812caaeb4829937f4599b51"), "name" : "ingredients2", "ingredientsNumber" : 1 }
{ "_id" : ObjectId("5812caaeb4829937f4599b52"), "name" : "ingredients3", "ingredientsNumber" : 2 }
{ "_id" : ObjectId("5812caaeb4829937f4599b53"), "name" : "ingredients4", "ingredientsNumber" : 1 }
{ "_id" : ObjectId("5812caaeb4829937f4599b54"), "name" : "ingredients5", "ingredientsNumber" : 2 }
{ "_id" : ObjectId("5812caaeb4829937f4599b57"), "name" : "ingredients6" }

货物:

  • 它只使用聚合,因此它应该更快