Mongoose中的子文件总和

目前我有这个模式。

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} }); 

数据库logging的例子

 { "_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,并把它放在总价格字段? 现在我正在考虑集合函数,但是我怀疑这是否是正确的方法。 我想我需要一个更新查询和求和方法在同一时间。 任何人都可以帮助我吗?

使用aggregate()函数,您可以运行以下pipe道以获得所需的结果:

 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)); }) 

在上面的stream水线中,第一步是$unwind操作符

 { "$unwind": "$products" } 

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

对于下一个pipe道阶段这是一个必要的操作, $group步骤是通过_id字段对拼合文档进行分组,从而有效地将非规范化文档重新组合到其原始模式。

$grouppipe道运算符类似于SQL的GROUP BY子句。 在SQL中,除非使用任何聚合函数,否则不能使用GROUP BY 。 同样的,你也必须在MongoDB中使用一个聚合函数(称为累加器)。 你可以在这里阅读关于累加器的更多信息。

在这个$group操作中,计算totalPrice和返回原始字段的逻辑是通过累加器 。 您可以通过总结每个组的每个小组的subTotal价值来获得totalPrice

 "totalPrice": { "$sum": "$products.subTotal } 

另一个expression

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

将使用$first从每个组的第一个文档返回一个userPurchased值。 因此在$unwind之前有效地重build原始文档模式

在这里需要注意的一点是,在执行一个pipe道的时候,MongoDB把pipe道操作员互相连接起来。 这里的“pipe道”是指Linux的含义:操作员的输出成为下一个操作员的input。 每个操作员的结果是一个新的文件集合。 所以Mongo执行上面的stream水线如下:

 collection | $unwind | $group => result 

作为一个方面说明,为了帮助理解stream水线或debugging它,如果您得到意想不到的结果,只用第一个stream水线运算符运行聚合。 例如,在mongo shell中运行聚合:

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

检查结果以查看productsarrays是否被正确解构。 如果这给出了预期的结果,添加下一个:

 db.cart.aggregate([ { "$unwind": "$products" }, { "$group": { "_id": "$_id", "products": { "$push": "$products" }, "userPurchased": { "$first": "$userPurchased" }, "totalPrice": { "$sum": "$products.subTotal" } } } ]) 

重复这些步骤,直到最后的pipe道步骤。


如果你想更新字段,那么你可以添加$outpipe道阶段作为最后一步。 这会将汇总pipe道的结果文档写入相同的集合,从而在技术上更新集合。

 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 ] 

UPDATE

为了执行更新和查询,您可以在聚合callback中发出find()调用来获取更新的json,即

 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)); }) }) 

使用Promise,你可以这样做

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

我不能确定这种方法是否比聚合更好,但是如果你想用虚拟方法来做:

 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()

您可以使用当前的Mongo 3.4版本和新的$ reduce汇总计算相同的值。

 db.cart.aggregate( [{ $project: { "_id": 1, "products": 1, "userPurchased": 1, "totalPrice": { $reduce: { input: "$products", initialValue: 0, in: { $add: ["$$value", "$$this.subTotal"] } } } } }, { "$out": "cart" }] );