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
字段对拼合文档进行分组,从而有效地将非规范化文档重新组合到其原始模式。
$group
pipe道运算符类似于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" } ])
检查结果以查看products
arrays是否被正确解构。 如果这给出了预期的结果,添加下一个:
db.cart.aggregate([ { "$unwind": "$products" }, { "$group": { "_id": "$_id", "products": { "$push": "$products" }, "userPurchased": { "$first": "$userPurchased" }, "totalPrice": { "$sum": "$products.subTotal" } } } ])
重复这些步骤,直到最后的pipe道步骤。
如果你想更新字段,那么你可以添加$out
pipe道阶段作为最后一步。 这会将汇总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" }] );