更新mongodb中的聚合结果?

时间:2014-03-22 01:21:04

标签: javascript mongodb aggregation-framework

考虑工作订单集合的以下架构。

    {"Activity #": "1111111",
    "Customer": "Last, First",
    "Tenure": "0 Year 2 Months",
    "Account #": "0000000",
    "lines": [{
            "Line#": "line#",
            "Product": "product",
            "Status": "status",
            "price": "price"
        },
        {
            "Line#": "line#",
            "Product": "product",
            "Status": "status",
            "price": "price"
        }]}

我可以使用以下查询获取所有已关闭的订单项。

db.workorders.aggregate({$match:{"Status":"Closed"}},{$unwind:"$lines"},{$match:{"lines.Status":"Closed"}},{$project:{lines:1}})

现在让我说我有另一个收藏价格。

    {"Product":"product", 
       "Price":"Price"}

如何通过匹配价格集合中的产品来更新已关闭的行项目价格。

提前致谢。

2 个答案:

答案 0 :(得分:1)

因此,尽管您可以使用聚合获得此结果,但问题在于这正是聚合的用途。因此,它用于查询结果。

虽然这样做"过滤"只是"匹配"数组中您需要的项目,下一个问题是您不能更新一次超过一个数组元素。所以为了更新"多个"项目,您唯一的选择是在更新期间替换整个数组。除非你已经拥有了数组的内容,否则你需要获取整个文档。

"第三"你在这里介绍的问题是你需要"查找"另一个集合,以匹配值。简而言之, MongoDB不会加入。那就是。

除非你改变你的架构以适应其他方式,否则你会坚持这个:

db.workorders.find({ "Status": "Closed", "lines.Status": "Closed" })
    .forEach(function(order) {
        for ( var i = 0; i < order.lines.length; i++ ) {
            if ( order.lines[i].Status == "Closed" ) {
               var prod = db.product.findOne(
                   { "product": order.lines[i].product });
               order.lines[i].price = prod.price;
            }
        }

        db.workorders.update(
            { "_id": order._id },
            { "$set": { "lines": order.lines } }
        );
})

或者这实际上在您的语言实现中有效。但这是一般的逻辑。

因此,在这种情况下,原始聚合语句对您不太好。即使使用匹配的&#34;工作人员&#34;这个结果有点多余,因为你无论如何都可以查询。

这里有一个用法有助于优化这一点,这就是改变聚合语句以获得您将要查找的唯一产品更新。然后,不是每次匹配产品时调用.findOne(),而是可以使用聚合到&#34;缓存&#34;的结果。将被击中的产品#34;在更新中。

var prodValues = db.workorders.aggregate([

    // Match the order 
    { "$match":{ "Status":"Closed" }},

    // Unwind the array
    { "$unwind":"$lines" },

    // Match only the closed lines
    { "$match" :{ "lines.Status":"Closed" } },

    // Group the products
    { "$group": {
        "_id": null,
        "products": { "$addToSet": "$lines.Product" }
    }}
])

然后,您可以将其提供给您的查询产品一次,以便获得&#34;价格缓存&#34;:

var prodCache = {};

db.products.find({ "Product": { "$in": prodValues.result.products } })
    .forEach(function(product) {
        prodCache[product.Product] = product.price;
});

并使用它代替每次都做一次查找。所以改变这些方面:

            if ( order.lines[i].Status == "Closed" )
                order.lines[i].price = 
                    prodCache[order.lines[i].Product].price;

所以至少这会给你&#34;一些&#34;从您的汇总声明中受益。

总体而言,当您必须更新阵列中的多个项时,最好更新整个阵列。至少到下一个版本(写作时),你可以在batch updates中完成。

答案 1 :(得分:0)

上面的当前答案包含过时的信息。

Mongo 可以使用$ out更新集合,该集合将查询并输出到您指定的集合,因此,如果您指定要集合的集合,它将对其进行更新。因此,您可以向嵌入式数组添加一个值,然后输出该新集合,从而向数组添加一个新值。

Mongo 可以加入,尽管我会尽可能避免。您可以在汇总中使用$ lookup来从其他集合中获取信息。如果您好奇的话,还可以使用Mongoose并使用其填充功能。注意:您不能将$ out包含在$ lookup管道中,因此请将$ out放在整体汇总的末尾。

您可以通过以下两种方式结合这两个原则:

db.workorders.aggregate({
$match:{"Status":"Closed"}},
{$unwind:"$lines"},
{$match:{"lines.Status":"Closed"}},
{
   $lookup:
     {
       from: Prices,
       localField: lines.price,
       foreignField: price,
       as: lines.price
     }
},
{
   $lookup:
     {
       from: Product,
       localField: lines.Product,
       foreignField: Product,
       as: lines.Product
     }
},
{ $output: "workorders" } // this will replace your collection with output from aggregate