mongodb multiple groups in one result

Deadly 提交于 2019-12-13 16:40:05

问题


I have documents similar to this stored in mongodb

{
 "_id":"transaction_id"
 "customer":"some customer",
 "order_date":Date('2011-01-01'),
 "delivery_date":Date('2011-01-15'),
 "amt":500.0,
 "qty":50
},
{
 "_id":"transaction_id"
 "customer":"some customer",
 "order_date":Date('2011-01-01'),
 "delivery_date":Date('2011-02-04'),
 "amt":500.0,
 "qty":50
}

I am looking to do some aggregation for order date and delivery date to plot the total quantity of inventory being ordered and delivered to each customer per month.

Granted I could run 2 aggregation queries to obtain what I want, but I was just wondering if it is possible to obtain a result that has 2 sets of groups with 1 command?

Expected result would be like:

results:[{
 _id:{
   customer:"some customer"
 },
 orders:[
  {
   year:2011,
   month:1,
   qty:100
  },
   ...
 ]
 deliveries:[
  {
   year:2011,
   month:1,
   qty:50
  },
  {
   year:2011,
   month:2,
   qty:50
  },
  ...
 ]
},...]

回答1:


You can do this in a single query, you just need to be a little creative with manipulating the documents and then essentially do two $group stages, adding by date first and then by the customer.

So first of all for current MongoDB versions 2.6 and above due to some of the operators used:

db.transactions.aggregate([

    // Project an additional array, stands for "order", "delivery"
    { "$project": {
        "_id": 0,
        "customer": 1,
        "order_date": 1,
        "delivery_date": 1,
        "qty": 1,
        "type": { "$literal": ["o","d"] }
    }},

    // Unwind that array, creates two documents by "type"
    { "$unwind": "$type" },

    // Group by "customer", "type" and date
    { "$group": {
        "_id": { 
            "customer": "$customer",
            "type": "$type",
            "year": { 
                "$year": { 
                    "$cond": [
                        { "$eq": [ "$type", "o" ] },
                        "$order_date",
                        "$delivery_date"
                    ]
                }
            },
            "month": { 
                "$month": { 
                    "$cond": [
                        { "$eq": [ "$type", "o" ] },
                        "$order_date",
                        "$delivery_date"
                    ]
                }
            }
        },
        "qty": { "$sum": "$qty" }
    }},

    // Group on the "customer" selecting which array to add to
    { "$group": {
        "_id": "$_id.customer",
        "orders": {
            "$push": {
                "$cond": [
                    { "$eq": [ "$_id.type", "o" ] },
                    {
                        "year": "$_id.year",
                        "month": "$_id.month",
                        "qty": "$qty"
                    },
                    false
                ]
            }
        },
        "deliveries": {
            "$push": {
                "$cond": [
                    { "$eq": [ "$_id.type", "d" ] },
                    {
                        "year": "$_id.year",
                        "month": "$_id.month",
                        "qty": "$qty"
                    },
                    false
                ]
            }
        }
    }},

    // Getting rid of the `false` values in there
    { "$project": {
        "orders": { "$setDifference": [ "$orders", [false] ] },
        "deliveries": { "$setDifference": [ "$deliveries", [false] ] },
    }},

    // But "sets" are not considered ordered, so sort them
    { "$unwind": "$orders" },
    { "$sort": { "orders.year": 1, "orders.month": 1 } }, 
    { "$group": {
        "_id": "$_id",
        "orders": { "$push": "$orders" },
        "deliveries": { "$first": "$deliveries" }
    }},
    { "$unwind": "$deliveries" },
    { "$sort": { "deliveries.year": 1, "deliveries.month": 1 } }, 
    { "$group": {
        "_id": "$_id",
        "orders": { "$first": "$orders" },
        "deliveries": { "$push": "$deliveries" }
    }}
)

Just doing that a little differently for pre 2.6 versions:

db.transactions.aggregate([

    // Project an additional array, stands for "order", "delivery"
    { "$project": {
        "_id": 0,
        "customer": 1,
        "order_date": 1,
        "delivery_date": 1,
        "qty": 1,
        "type": { "$cond": [ 1, ["o","d"], 0 ] }
    }},

    // Unwind that array, creates two documents by "type"
    { "$unwind": "$type" },

    // Group by "customer", "type" and date
    { "$group": {
        "_id": { 
            "customer": "$customer",
            "type": "$type",
            "year": { 
                "$year": { 
                    "$cond": [
                        { "$eq": [ "$type", "o" ] },
                        "$order_date",
                        "$delivery_date"
                    ]
                }
            },
            "month": { 
                "$month": { 
                    "$cond": [
                        { "$eq": [ "$type", "o" ] },
                        "$order_date",
                        "$delivery_date"
                    ]
                }
            }
        },
        "qty": { "$sum": "$qty" }
    }},

    // Group on the "customer" selecting which array to add to
    { "$group": {
        "_id": "$_id.customer",
        "orders": {
            "$push": {
                "$cond": [
                    { "$eq": [ "$_id.type", "o" ] },
                    {
                        "year": "$_id.year",
                        "month": "$_id.month",
                        "qty": "$qty"
                    },
                    false
                ]
            }
        },
        "deliveries": {
            "$push": {
                "$cond": [
                    { "$eq": [ "$_id.type", "d" ] },
                    {
                        "year": "$_id.year",
                        "month": "$_id.month",
                        "qty": "$qty"
                    },
                    false
                ]
            }
        }
    }},

    // Filter `false` and sort on date
    { "$unwind": "$orders" },
    { "$match": { "orders": { "$ne": false } } },
    { "$sort": { "orders.year": 1, "orders.month": 1 } }, 
    { "$group": {
        "_id": "$_id",
        "orders": { "$push": "$orders" },
        "deliveries": { "$first": "$deliveries" }
    }},
    { "$unwind": "$deliveries" },
    { "$match": { "deliveries": { "$ne": false } } },
    { "$sort": { "deliveries.year": 1, "deliveries.month": 1 } }, 
    { "$group": {
        "_id": "$_id",
        "orders": { "$first": "$orders" },
        "deliveries": { "$push": "$deliveries" }
    }}

])

Basically to sum up the approach here, what you are doing is duplicating every document and assigning a "type" representing "order" or "delivery". Then as you group by "customer" and "date" and "type" you conditionally decide which "date" to choose based on the current type, and just sum up the "qty" under that key.

Since the result is an array of "orders" and "deliveries" per customer you then conditionally $push to that array either the document values or false depending on what the current "type" of the document is to each array.

Finally, since these arrays now contain values for false as well as the required documents you filter out those values and make sure your arrays are in the correct "date" order if you actually require that.

Yes the listings have more than two $group stages, the heavy lifting is actually done in two groupings with the others just there for array manipulation if you require it, but it gives you exact and ordered results.

So that is likely not the first approach you might have thought of, but shows some of the interesting transformation ideas you can use with various aggregation operators in order to solve the problem. Which this does :)




回答2:


If I understood you correctly:

db.collName.aggregate({$project:{
                        customer:1,
                        order:{ 
                                qty:"$qty", 
                                year:{$year:"$order_date"}, 
                                month:{$month:"$order_date"}
                              },
                        delivery:{
                                 qty:"$qty", 
                                 year:{$year:"$delivery_date"}, 
                                 month:{$month:"$delivery_date"}
                              } 
                           }
                 },
                 {$group:{
                           _id:{
                              customer:"$customer"
                           },
                           orders: { $push:"$order" },
                           deliveries:{ $push:"$delivery"}
                          }
                 });


来源:https://stackoverflow.com/questions/23745097/mongodb-multiple-groups-in-one-result

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!