node.js - Mongo/Mongoose 聚合 - $redact 和 $cond 问题

标签 node.js mongodb mongoose mongodb-query aggregation-framework

我很幸运能够得到另一个 SO 问题的精彩答案 Mongo / Mongoose - Aggregating by Date来自@chridam,它给出了一组文档,例如:

{ "_id" : ObjectId("5907a5850b459d4fdcdf49ac"), "amount" : -33.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-04-26T23:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.581Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ba"), "amount" : -61.3, "name" : "Amazon", "method" : "VIS", "date" : ISODate("2017-03-23T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.592Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ce"), "amount" : -3.3, "name" : "Tesco", "method" : "VIS", "date" : ISODate("2017-03-15T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.601Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49cc"), "amount" : -26.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-16T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.600Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49f7"), "amount" : -63.3, "name" : "Sky", "method" : "VIS", "date" : ISODate("2017-03-02T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.617Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49be"), "amount" : -3.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-22T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.593Z"), "category" : "Not Set", "__v" : 0 }

需要一个按供应商、年、月和周汇总支出的查询。查询在下面,它几乎可以正常工作,但是当我在我的应用程序中使用它时,我注意到一个严重的问题

db.statements.aggregate([
  { "$match": { "name": "RINGGO" } },
  {
  "$redact": {
      "$cond": [
          {
              "$and": [
                 { "$eq": [{ "$year": "$date" },  2017  ]}, // within my route this uses parseInt(req.params.year)
                 { "$eq": [{ "$month": "$date" }, 3 ]}, // within my route this uses parseInt(req.params.month)
                 { "$eq": [{ "$week": "$date" },  12  ]} // within my route this uses parseInt(req.params.week)
            ]
        },
        "$$KEEP",
        "$$PRUNE"
    ]
}
},{
    "$group": {
        "_id": {
            "name": "$name",
            "year": { "$year": "$date" },
            "month": { "$month": "$date" },
            "week": { "$week": "$date" }
        },
        "total": { "$sum": "$amount" }
    }
},
{
    "$group": {
        "_id": {
            "name": "$_id.name",
            "year": "$_id.year"
        },
        "YearlySpends": { "$push": "$total" },
        "totalYearlyAmount": { "$sum": "$total" },
        "data": { "$push": "$$ROOT" }
    }
},
{ "$unwind": "$data" },
{
    "$group": {
        "_id": {
            "name": "$_id.name",
            "month": "$data._id.month"
        },
        "YearlySpends": { "$first": "$YearlySpends" },
        "totalYearlyAmount": { "$first": "$totalYearlyAmount" },
        "MonthlySpends": { "$push": "$data.total" },
        "totalMonthlyAmount": { "$sum": "$data.total" },
        "data": { "$push": "$data" }
    }
},
{ "$unwind": "$data" },
{
    "$group": {
        "_id": {
            "name": "$_id.name",
            "week": "$data._id.week"
        },
        "YearlySpends": { "$first": "$YearlySpends" },
        "totalYearlyAmount": { "$first": "$totalYearlyAmount" },
        "MonthlySpends": { "$first": "$MonthlySpends" },
        "totalMonthlyAmount": { "$first": "$totalMonthlyAmount" },
        "WeeklySpends": { "$push": "$data.total" },
        "totalWeeklyAmount": { "$sum": "$data.total" },
        "data": { "$push": "$data" }
    }
},
{ "$unwind": "$data" },
{
    "$group": {
        "_id": "$data._id",
        "YearlySpends": { "$first": "$YearlySpends" },
        "totalYearlyAmount": { "$first": "$totalYearlyAmount" },
        "MonthlySpends": { "$first": "$MonthlySpends" },
        "totalMonthlyAmount": { "$first": "$totalMonthlyAmount" },
        "WeeklySpends": { "$first": "$WeeklySpends" },
        "totalWeeklyAmount": { "$first": "$totalWeeklyAmount" }
    }
}
])

运行此查询返回

{ "_id" :
 { "name" : "RINGGO", 
   "year" : 2017, 
   "month" : 3, 
   "week" : 12 }, 
   "YearlySpends" : [ -9.6 ], 
   "totalYearlyAmount" : -9.6, 
   "MonthlySpends" : [ -9.6 ], 
   "totalMonthlyAmount" : -9.6, 
   "WeeklySpends" : [ -9.6 ], 
   "totalWeeklyAmount" : -9.6 
}

当我转而想查看当月的支出

"$cond": [
          {
            "$and": [
                 { "$eq": [{ "$year": "$date" },  2017  ]},
                 { "$eq": [{ "$month": "$date" }, 3 ]}
            ]
          },
        "$$KEEP",
        "$$PRUNE"
      ]

我得到:

{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 12 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -9.6 ], "totalWeeklyAmount" : -9.6 }
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 9 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -3.3 ], "totalWeeklyAmount" : -3.3 }
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 11 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -9.6 ], "totalWeeklyAmount" : -9.6 }
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 13 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -3.3 ], "totalWeeklyAmount" : -3.3 }

然而,当我运行一个简单的 db.statements.find({"name":"RINGGO"}) 我得到:

{ "_id" : ObjectId("5907a5850b459d4fdcdf49ac"), "amount" : -3.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-26T23:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.581Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ba"), "amount" : -6.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-23T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.592Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ce"), "amount" : -3.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-15T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.601Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49cc"), "amount" : -6.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-16T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.600Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49f7"), "amount" : -3.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-02T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.617Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49be"), "amount" : -3.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-22T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.593Z"), "category" : "Not Set", "__v" : 0 }

因此您可以看到,与按名称查找的输出中显示的项目相比,先前输出中 MonthlySpends 中的项目数量不同。您还可以看到,一些值在不应该的情况下在 MonthlySpends 中被加在一起。

理想情况下,我希望获得以下输出: 当我有 $redact 包含:

"$cond": [
        {
            "$and": [
                 { "$eq": [{ "$year": "$date" },  2017  ]}, 
                 { "$eq": [{ "$month": "$date" }, 3 ]}, 
                 { "$eq": [{ "$week": "$date" },  12  ]} 
            ]
        },
        "$$KEEP",
        "$$PRUNE"
    ]

返回

{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 12 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -9.6 ], "totalWeeklyAmount" : -9.6 }

当我有 $redact 包含:

"$cond": [
        {
            "$and": [
                 { "$eq": [{ "$year": "$date" },  2017  ]}, 
                 { "$eq": [{ "$month": "$date" }, 3 ]},
            ]
        },
        "$$KEEP",
        "$$PRUNE"
        ]

返回

{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997 }

当我有 $redact 包含:

"$cond": [
        {
            "$and": [
                 { "$eq": [{ "$year": "$date" },  2017  ]}
            ]
        },
        "$$KEEP",
        "$$PRUNE"
        ]

返回

{ "_id" : { "name" : "RINGGO", "year" : 2017 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997}

在这方面非常需要任何帮助。我试过修改查询,但恐怕我对它的理解还不足以正确修改它。

我的 Mongoose 版本是 ^4.9.5,我的 mongo 是 3.4.2

最佳答案

您可以在 3.4 版本中尝试使用 $facet$addFields 进行并行聚合。

这将降低整体复杂性,您可以同时使用自己的匹配输入运行分组。

下面的代码基于请求对象动态构建聚合管道。

// Sample request
var request = {
  "name":"RINGGO",
  "year": 2017,
  "month":3,
  "week":12
};

// Build initial match document on name

var match1 = {
  name: request["name"]
};

// Build project & facet document for date based aggregation

var addFields = {};
var facet = {};

// Add year followed by year facet

if (request["year"]) {
    addFields["year"] = { "$year": "$date" },
    facet["Yearly"] = 
      [
        {
          "$match":{ "year": request["year"] }
        },
        {
          "$group": {
            "_id": {
              "name": "$name",
              "year": "$year"
            },
            "spend": { "$push":"$amount" },
            "total": { "$sum": "$amount" }
        }
      }
    ];
}

// Add month followed by month facet

if (request["month"]) {
    addFields["month"] = { "$month": "$date" };
    facet["Monthly"] = 
      [
        {
          "$match":{ "month": request["month"] }
        },
        {
          "$group": {
            "_id": {
              "name": "$name",
              "month": "$month"
            },
            "spend": { "$push":"$amount" },
            "total": { "$sum": "$amount" }
         }
      }
    ];
}

// Add week followed by week facet

if (request["week"]) {
    addFields["week"] = { "$week": "$date" };
    facet["Weekly"] = 
      [
        {
          "$match":{ "week": request["week"] }
        },
        {
          "$group": {
            "_id": {
              "name": "$name",
              "week": "$week"
            },
            "spend": { "$push":"$amount" },
            "total": { "$sum": "$amount" }
         }
      }
    ];
}

// Use aggregate builder

statements.aggregate()
        .match(match1)
        .append({"$addFields": addFields}) // No addFields stage in mongoose builder
        .facet(facet)
        .exec(function(err, data) {});

name/year/month/week 标准的 Mongo Shell 查询。

db.statements.aggregate({
    '$match': {
        name: 'RINGGO'
    }
}, {
    '$addFields': {
        year: {
            '$year': '$date'
        },
        month: {
            '$month': '$date'
        },
        week: {
            '$week': '$date'
        }
    }
}, {
    '$facet': {
        Yearly: [{
                '$match': {
                    year: 2017
                }
            },
            {
                '$group': {
                    _id: {
                        name: '$name',
                        year: '$year'
                    },
                    spend: {
                        '$push': '$amount'
                    },
                    total: {
                        '$sum': '$amount'
                    }
                }
            }
        ],
        Monthly: [{
                '$match': {
                    month: 3
                }
            },
            {
                '$group': {
                    _id: {
                        name: '$name',
                        month: '$month'
                    },
                    spend: {
                        '$push': '$amount'
                    },
                    total: {
                        '$sum': '$amount'
                    }
                }
            }
        ],
        Weekly: [{
                '$match': {
                    week: 12
                }
            },
            {
                '$group': {
                    _id: {
                        name: '$name',
                        week: '$week'
                    },
                    spend: {
                        '$push': '$amount'
                    },
                    total: {
                        '$sum': '$amount'
                    }
                }
            }
        ]
    }
})

示例响应

    {
    "Yearly": [{
        "_id": {
            "name": "RINGGO",
            "year": 2017
        },
        "spend": [-3.3, -6.3, -3.3, -6.3, -3.3, -3.3],
        "total": -25.799999999999997
    }],
    "Monthly": [{
        "_id": {
            "name": "RINGGO",
            "month": 3
        },
        "spend": [-3.3, -6.3, -3.3, -6.3, -3.3, -3.3],
        "total": -25.799999999999997
    }],
    "Weekly": [{
        "_id": {
            "name": "RINGGO",
            "week": 12
        },
        "spend": [-6.3, -3.3],
        "total": -9.6
    }]
}

您可以为 Year/MonthYear 输入值运行类似的聚合。

So you can see that there is a different number of items in MonthlySpends in previous output compared to that shown in the output from the find by name. Also you can see that some of the values are being summed together in MonthlySpends when they shouldn't be.

这发生在 $group 1 中,其中 $week 聚合将两个日期 [15, 16] 中的每一个汇总到第 11 周和其他两个日期 [22, 23] ] 数额进入第 12 周后显示为 MonthySpends 中的总计。

关于node.js - Mongo/Mongoose 聚合 - $redact 和 $cond 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43950745/

相关文章:

node.js - 我们如何将 --max-old-space-size 传递给forever 和 npm run start

javascript - 断言selenium webdriver Node js

javascript - 如何使用Mysql和node.js进行同步数据库查询?

mongodb - 如何在 Meteor 服务器端做 mongo 组

node.js - 如何导出csv nodejs

javascript - 为什么 bson.dumps 转义我的字典列表(jsons)

mongodb - 是否可以创建一个 Mongoose Hook 以将投影应用于所有查询

validation - Mongoose 需要 Array

javascript - 为什么在Nodejs Event Loop的fs.readFile()之前先执行setImmediate()?

node.js - 没有模型或模式的 Mongoose