按类别分组,然后按周或按月分组

时间:2016-08-18 12:40:36

标签: mongodb mongodb-query aggregation-framework

我的文件如下:

{
    category: "1",
    timestamp: ISODate("2016-07-16T00:00:00.000Z"),
    amount: 0
},
{
    category: "1",
    timestamp: ISODate("2016-08-18T00:00:00.000Z"),
    amount: 15
},
{
    category: "1",
    timestamp: ISODate("2016-08-01T00:00:00.000Z"),
    amount: 5
},
{
    category: "2",
    timestamp: ISODate("2016-08-18T00:00:00.000Z"),
    amount: 10
}

现在我想按类别第一次分组(已经有效):

{ "$match" : { "timestamp" : { "$gt" : FROM , "$lt" : TO }}},
{ "$sort" : { "timestamp" : 1 }},
{ "$group" : {
    "_id" : "$category",
    "data" : { "$push" : { "timestamp" : "$timestamp" , "amount" : "$amount" }}
}}

然后将这些对象分组到data数组中。要获得每周最高金额(或月份 - 取决于用户输入)。

结果应该是这样的(按月分组):

{
    _id: "1",
    data: [
        {
            timestamp: "2016-07",    // could also be an ISODate with
            amount: 0                // first (or last) day of month
        },                           // if that makes things easier
        {
            timestamp: "2016-08",
            amount: 15
        }
    ]
},
{
    _id: "2",
    data: [
        {
            timestamp: "2016-08",
            amount: 10
        }
    ]
}

我尝试unwind data数组然后重新分组,但这导致了一团糟。

希望您有一些好主意/解决方案来实现这一目标。

编辑:附加问题:

我在category上放了一个索引,对$match起作用。在timestamp上放置一个索引进行排序(因为插入顺序可能与时间戳顺序不同)或者这个索引在聚合中是否有任何影响会不会有用呢?

2 个答案:

答案 0 :(得分:3)

我已经接受了Styvane的回答(再次感谢!)并简化了一下:

{$match: { timestamp: { $gt: FROM , $lt: TO }}},
{$group: {
    _id: {
        id: "$category",
        timestamp: { $concat: [
            { $toLower: { $year:"$timestamp" } },
            "-",
            { $toLower: { $month: "$timestamp" } }
        ] }
    },
    amount: { $max: "$amount" }
}},
{$sort: { "_id.timestamp": 1 } },
{$group: {
    _id: "$_id.id",
    data: { $push: { timestamp: "$_id.timestamp", amount: "$amount" } }
}}

我在第一个$sort之前尝试$group,但这确实给出了意想不到的结果。虽然我只是将$sort放在$group个阶段之间。这样,timestamp上的索引就不再重要了。

答案 1 :(得分:1)

$sort阶段之后,您需要$group by" category"然后$unwind"数据"领域。

var group1 = { "$group": { 
    "_id": "$category", 
    "data": { 
        "$push": { 
            "timestamp": "$timestamp", 
            "amount": "$amount"
        }
    }
}};

var unwind = { "$unwind": "$data"};

从那里开始,您需要重新$group您的文档,但这次您不仅需要考虑timestamp字段,还需要考虑_id字段,并在此帮助下$toLower运算符,您可以将年和月值转换为可以使用$concat运算符连接的字符串。

您还可以使用$sum返回该组的总和。

var group2 = { "$group": { 
    "_id": { 
        "id": "$_id", 
        "timestamp": { 
            "$concat": [ 
                { "$toLower": { "$year": "$data.timestamp" } }, 
                "-", 
                { "$toLower": { "$month": "$data.timestamp" } }
            ]
    }}, 
    "amount": { "$sum": "$data.amount" }
}}

最后一个阶段是另一个$group阶段,您只需按先前的_id.id值对文档进行分组,然后使用$push累加器运算符返回数据数组。

var group3 = { "$group": { 
    "_id": "$_id.id", 
    "data": { 
        "$push": { 
            "timestamp": "$_id.timestamp", 
            "amount": "$amount" 
        }
    }
}};

您的最终管道将如下所示:

db.collection.aggregate(
    [
        // $match and `$sort here
        group1,
        unwind,
        group2,
        group3
    ]
)

使用$facet运算符即可在即将推出的MongoDB版本中改进此查询。

db.collection.aggregate([
    // $match and `$sort here
    { "$facet": { "data": [ group1, unwind, group2, group3 ] }
])