node.js - Mongoose 中子文档的总和

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

数据库记录示例

{
    "_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
}

有什么方法可以将所有小计求和并放入总价字段中吗?现在我正在考虑聚合函数,但我怀疑这是否是正确的方法。我想我同时需要更新查询和求和方法。有人可以帮我吗?

最佳答案

使用 aggregate() 函数,您可以运行以下使用 $sum 的管道运算符以获得所需的结果:

const results = await Cart.aggregate([
    { "$addFields": {
        "totalPrice": {
            "$sum": "$products.subTotal"
        }
    } },
]);

console.log(JSON.stringify(results, null, 4));

对应的更新操作如下:

db.carts.updateMany(
   { },
   [
        { "$set": {
            "totalPrice": {
                "$sum": "$products.subTotal"
            }
        } },
    ]
)

或者,如果使用 MongoDB 3.2 及更早版本,则 $sum仅在$group阶段可用,您可以这样做

const 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 。您可以通过使用 $sum 将每组中每个单独的 subTotal 值相加来获得 totalPrice,如下所示:

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

另一种表达方式

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

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

这里需要注意的一件事是,在执行管道时,MongoDB 将运算符通过管道相互连接。这里的“Pipe”取Linux的含义:一个运算符的输出成为下一个运算符的输入。每个运算符的结果都是一个新的文档集合。所以 Mongo 执行上述管道如下:

collection | $unwind | $group => result
<小时/>

顺便说一句,为了帮助理解管道或在获得意外结果时对其进行调试,请仅使用第一个管道运算符运行聚合。例如,在 mongo shell 中运行聚合:

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

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

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

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

<小时/>

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

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
]
<小时/>

更新

要同时执行更新和查询,您可以在聚合回调中发出 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));
});

关于node.js - Mongoose 中子文档的总和,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41501752/

相关文章:

node.js - meteor 构建内存不足

javascript - 就像 mongoose-paginate 的过滤器一样

Java,java.util.HashMap 无法转换为 org.bson.BSONObject

node.js - 在Webpack中访问动态端口号

node.js - nodejs : Access variables in routes

javascript - 即使在 return 语句之后代码仍在执行吗?

mongodb:使用包含文档和子文档的where子句删除子文档

node.js - Passport : Authenticating the whole app instead of some requests

javascript - 无法通过 Express 路由从 Mongodb 中删除文档

node.js - 在 Keystone.js 中显示带有描述的图库图像时出现问题