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

时间:2017-05-13 08:05:21

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

我很幸运能够从@chridam获得另一个SO问题Mongo / Mongoose - Aggregating by Date的精彩答案,该问题给出了一组文档:

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

1 个答案:

答案 0 :(得分:0)

您可以$facet$addFields一起尝试3.4版本中的并行聚合。

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

以下代码根据请求对象动态构建聚合管道。

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

Mongo Shell查询name/year/month/week条件。

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输入值运行类似的聚合。

  

所以你可以看到有不同数量的物品   以前输出中的MonthlySpends与输出中显示的相比   从名字找到。你也可以看到一些值   当它们不应该在MonthlySpends中汇总时。

这种情况发生在$group 1,其中$week汇总将两个日期[15,16]中的每一个汇总到第11周,其他两个日期[22,23]汇总到第12周以后显示在MonthySpends中总计为总计。