Mongoose中子文档的总和

时间:2017-01-06 08:29:15

标签: node.js mongodb express mongoose aggregation-framework

目前我有这个架构。

var cartSchema = new Schema({
    userPurchased: {type: Schema.Types.ObjectId, ref: 'users'},
    products: [
        {
            product: {type: Schema.Types.ObjectId, ref: 'products'},
            size: {type: String, required: true},
            quantity: {type: Number, required: true},
            subTotal: {type: Number, required: true}
        }
    ],
    totalPrice: {type: Number, default: 0, required: true}
});

db记录的示例

{
    "_id": {
        "$oid": "586f4be94d3e481378469a08"
    },
    "userPurchased": {
        "$oid": "586dca1f5f4a7810fc890f97"
    },
    "totalPrice": 0,
    "products": [
        {
            "product": {
                "$oid": "58466e8e734d1d2b0ceeae00"
            },
            "size": "m",
            "quantity": 5,
            "subTotal": 1995,
            "_id": {
                "$oid": "586f4be94d3e481378469a09"
            }
        },
        {
            "subTotal": 1197,
            "quantity": 3,
            "size": "m",
            "product": {
                "$oid": "58466e8e734d1d2b0ceeae01"
            },
            "_id": {
                "$oid": "586f4ef64d3e481378469a0a"
            }
        }
    ],
    "__v": 0
}

有没有办法将所有subTotal加起来并将其放入总价格字段?现在我正在考虑聚合函数,但我怀疑它在这里是正确的方法。我想我需要同时更新查询和求和方法。有人可以帮我吗?

2 个答案:

答案 0 :(得分:1)

使用 aggregate() 功能,您可以运行以下管道以获得所需的结果:

var pipeline = [
    { "$unwind": "$products" },
    {
        "$group": {
            "_id": "$_id",
            "products": { "$push": "$products" },
            "userPurchased": { "$first": "$userPurchased" },
            "totalPrice": { "$sum": "$products.subTotal" }
        }
    }
]

Cart.aggregate(pipeline)
    .exec(function(err, results){
        if (err) throw err;
        console.log(JSON.stringify(results, null, 4));
    })

在上面的管道中,第一步是 $unwind 运算符

{ "$unwind": "$products" }

当数据存储为数组时非常方便。当展开运算符应用于列表数据字段时,它将为应用展开的列表数据字段的每个元素生成新记录。它基本上使数据变平。

这是下一个管道阶段的必要操作, $group 步骤,您可以通过_id字段对展平的文档进行分组,从而有效地重新组合非规范化文档他们原来的架构。

$group 管道运算符类似于SQL的GROUP BY子句。在SQL中,除非使用任何聚合函数,否则不能使用GROUP BY。同样,您必须在MongoDB中使用聚合函数(称为累加器)。您可以阅读有关accumulators here

的更多信息

在此 $group 操作中,计算totalPrice并返回原始字段的逻辑是通过accumulators。您可以通过将 totalPrice 的每个组的每个subTotal值总结为$sum得到"totalPrice": { "$sum": "$products.subTotal }

"userPurchased": { "$first": "$userPurchased" },

另一个表达

userPurchased

将使用 $first 从每个组的第一个文档中返回collection | $unwind | $group => result 值。因此,在 $unwind

之前有效地重建原始文档架构

这里需要注意的一点是,在执行管道时,MongoDB会将运营商相互管道化。这里的“管道”采用Linux的含义:运算符的输出成为以下运算符的输入。每个运算符的结果是一个新的文档集合。所以Mongo按如下方式执行上述管道:

db.cart.aggregate([
    { "$unwind": "$products" }
])

作为旁注,为了帮助理解管道或调试它,如果您得到意外结果,只需使用第一个管道运算符运行聚合。例如,在mongo shell中运行聚合为:

products

检查结果以查看db.cart.aggregate([ { "$unwind": "$products" }, { "$group": { "_id": "$_id", "products": { "$push": "$products" }, "userPurchased": { "$first": "$userPurchased" }, "totalPrice": { "$sum": "$products.subTotal" } } } ]) 数组是否正确解构。如果这给出了预期结果,请添加下一个:

var pipeline = [
    { "$unwind": "$products" },
    {
        "$group": {
            "_id": "$_id",
            "products": { "$push": "$products" },
            "userPurchased": { "$first": "$userPurchased" },
            "totalPrice": { "$sum": "$products.subTotal" }
        }
    },
    { "$out": "cart" } // write the results to the same underlying mongo collection
]

重复这些步骤,直到进入最后的管道步骤。

如果您要更新该字段,则可以添加 $out 管道阶段作为最后一步。这会将聚合管道的结果文档写入同一集合,从而在技术上更新集合。

find()

<强>更新

要执行更新和查询,您可以在聚合回调中发出Cart.aggregate(pipeline) .exec(function(err, results){ if (err) throw err; Cart.find().exec(function(err, docs){ if (err) return handleError(err); console.log(JSON.stringify(docs, null, 4)); }) }) 调用以获取更新的json,即

Cart.aggregate(pipeline).exec().then(function(res)
    return Cart.find().exec();
).then(function(docs){  
    console.log(JSON.stringify(docs, null, 4));
});

使用Promise,您可以选择

FastCGI sent in stderr: "PHP message: PHP Warning:  file_get_contents(http://instance-data/latest/user-data): failed to open stream: HTTP request failed! HTTP/1.0 404 Not Found

答案 1 :(得分:0)

我无法确切地说这种方法是否比聚合更好,但是如果你想用虚拟方法做这件事:

cartSchema.virtual('totalPrice').get(function () {
     return this.products.map(p => p.subTotal).reduce((a, b) => a + b);
});

但是关心:

  

如果您使用toJSON()或toObject()(或在mongoose文档上使用JSON.stringify()),默认情况下mongoose将不包含虚拟内容。将{virtuals:true}传递给toObject()或toJSON()