首页 文章

MongoDB在每日分组中聚合

提问于
浏览
41

我在mongo中有一些看起来像这样的文档:

{
  _id : ObjectId("..."),
  "make" : "Nissan",
  ..
},
{
  _id : ObjectId("..."),
  "make" : "Nissan",
  "saleDate" :  ISODate("2013-04-10T12:39:50.676Z"),
  ..
}

理想情况下,我希望能够计算出每天销售的车辆数量 . 然后,我想要查看今天,或者像过去七天这样的窗口 .

我能用一些丑陋的代码完成日常视图

db.inventory.aggregate(
  { $match : { "saleDate" : { $gte: ISODate("2013-04-10T00:00:00.000Z"), $lt: ISODate("2013-04-11T00:00:00.000Z")  } } } ,
  { $group : { _id : { make : "$make", saleDayOfMonth : { $dayOfMonth : "$saleDate" } }, cnt : { $sum : 1 } } }
)

然后产生结果

{
  "result" : [
    {
      "_id" : {
        "make" : "Nissan",
        "saleDayOfMonth" : 10
      },
      "cnt" : 2
    },
    {
      "_id" : {
        "make" : "Toyota",
        "saleDayOfMonth" : 10
      },
      "cnt" : 4
    },
  ],
  "ok" : 1
}

所以这没关系,但我更希望不必更改查询中的两个日期时间值 . 然后,正如我上面提到的,我希望能够运行此查询(再次,无需每次都修改它),并查看在过去一周内按天分类的相同结果 .

哦,这是我一直用于查询的示例数据

db.inventory.save({"make" : "Nissan","saleDate" :  ISODate("2013-04-10T12:39:50.676Z")});
db.inventory.save({"make" : "Nissan"});
db.inventory.save({"make" : "Nissan","saleDate" :  ISODate("2013-04-10T11:39:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-09T11:39:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-10T11:38:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-10T11:37:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-10T11:36:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-10T11:35:50.676Z")});

凯文,提前谢谢

3 回答

  • 3

    在Mongo 2.8 RC2中有一个新的数据聚合运算符:$dateToString可用于按日分组,结果只有"YYYY-MM-DD":

    文档中的示例:

    db.sales.aggregate(
      [
         {
             $project: {
                    yearMonthDay: { $dateToString: { format: "%Y-%m-%d", date: "$date" } },
                    time: { $dateToString: { format: "%H:%M:%S:%L", date: "$date" } }
             }
         }
      ]
    )
    

    将导致:

    { "_id" : 1, "yearMonthDay" : "2014-01-01", "time" : "08:15:39:736" }
    
  • 59

    UPDATE 更新后的答案基于3.6中的日期功能,并显示如何在没有销售的范围内包含日期(包括我在内的任何原始答案中未提及) .

    样本数据:

    db.inventory.find()
    { "_id" : ObjectId("5aca30eefa1585de22d7095f"), "make" : "Nissan", "saleDate" : ISODate("2013-04-10T12:39:50.676Z") }
    { "_id" : ObjectId("5aca30eefa1585de22d70960"), "make" : "Nissan" }
    { "_id" : ObjectId("5aca30effa1585de22d70961"), "make" : "Nissan", "saleDate" : ISODate("2013-04-10T11:39:50.676Z") }
    { "_id" : ObjectId("5aca30effa1585de22d70962"), "make" : "Toyota", "saleDate" : ISODate("2013-04-09T11:39:50.676Z") }
    { "_id" : ObjectId("5aca30effa1585de22d70963"), "make" : "Toyota", "saleDate" : ISODate("2013-04-10T11:38:50.676Z") }
    { "_id" : ObjectId("5aca30effa1585de22d70964"), "make" : "Toyota", "saleDate" : ISODate("2013-04-10T11:37:50.676Z") }
    { "_id" : ObjectId("5aca30effa1585de22d70965"), "make" : "Toyota", "saleDate" : ISODate("2013-04-10T11:36:50.676Z") }
    { "_id" : ObjectId("5aca30effa1585de22d70966"), "make" : "Toyota", "saleDate" : ISODate("2013-04-10T11:35:50.676Z") }
    { "_id" : ObjectId("5aca30f9fa1585de22d70967"), "make" : "Toyota", "saleDate" : ISODate("2013-04-11T11:35:50.676Z") }
    { "_id" : ObjectId("5aca30fffa1585de22d70968"), "make" : "Toyota", "saleDate" : ISODate("2013-04-13T11:35:50.676Z") }
    { "_id" : ObjectId("5aca3921fa1585de22d70969"), "make" : "Honda", "saleDate" : ISODate("2013-04-13T00:00:00Z") }
    

    startDateendDate 定义为变量并在聚合中使用它们:

    startDate = ISODate("2013-04-08T00:00:00Z");
    endDate = ISODate("2013-04-15T00:00:00Z");
    
    db.inventory.aggregate([
      { $match : { "saleDate" : { $gte: startDate, $lt: endDate} } },
      {$addFields:{
         saleDate:{$dateFromParts:{
                      year:{$year:"$saleDate"},
                      month:{$month:"$saleDate"},
                      day:{$dayOfMonth:"$saleDate"}
         }},
         dateRange:{$map:{
            input:{$range:[0, {$subtract:[endDate,startDate]}, 1000*60*60*24]},
            in:{$add:[startDate, "$$this"]}
         }}
      }},
      {$unwind:"$dateRange"},
      {$group:{
         _id:"$dateRange", 
         sales:{$push:{$cond:[
                    {$eq:["$dateRange","$saleDate"]},
                    {make:"$make",count:1},
                    {count:0}
         ]}}
      }},
      {$sort:{_id:1}},
      {$project:{
         _id:0,
         saleDate:"$_id",
         totalSold:{$sum:"$sales.count"},
         byBrand:{$arrayToObject:{$reduce:{
            input: {$filter:{input:"$sales",cond:"$$this.count"}},
            initialValue: {$map:{input:{$setUnion:["$sales.make"]}, in:{k:"$$this",v:0}}}, 
            in:{$let:{
               vars:{t:"$$this",v:"$$value"},
               in:{$map:{
                  input:"$$v",
                  in:{
                     k:"$$this.k",
                     v:{$cond:[
                         {$eq:["$$this.k","$$t.make"]},
                         {$add:["$$this.v","$$t.count"]},
                         "$$this.v"
                     ]}
                  }
               }}
            }}
         }}}
      }}
    ])
    

    在样本数据上,这给出了结果:

    { "saleDate" : ISODate("2013-04-08T00:00:00Z"), "totalSold" : 0, "byBrand" : {  } }
    { "saleDate" : ISODate("2013-04-09T00:00:00Z"), "totalSold" : 1, "byBrand" : { "Toyota" : 1 } }
    { "saleDate" : ISODate("2013-04-10T00:00:00Z"), "totalSold" : 6, "byBrand" : { "Nissan" : 2, "Toyota" : 4 } }
    { "saleDate" : ISODate("2013-04-11T00:00:00Z"), "totalSold" : 1, "byBrand" : { "Toyota" : 1 } }
    { "saleDate" : ISODate("2013-04-12T00:00:00Z"), "totalSold" : 0, "byBrand" : {  } }
    { "saleDate" : ISODate("2013-04-13T00:00:00Z"), "totalSold" : 2, "byBrand" : { "Honda" : 1, "Toyota" : 1 } }
    { "saleDate" : ISODate("2013-04-14T00:00:00Z"), "totalSold" : 0, "byBrand" : {  } }
    

    这个聚合也可以用两个 $group 阶段和一个简单的 $project 而不是 $group 和一个复杂的 $project 来完成 . 这里是:

    db.inventory.aggregate([
       {$match : { "saleDate" : { $gte: startDate, $lt: endDate} } },
       {$addFields:{saleDate:{$dateFromParts:{year:{$year:"$saleDate"}, month:{$month:"$saleDate"}, day:{$dayOfMonth : "$saleDate" }}},dateRange:{$map:{input:{$range:[0, {$subtract:[endDate,startDate]}, 1000*60*60*24]},in:{$add:[startDate, "$$this"]}}}}},
       {$unwind:"$dateRange"},
       {$group:{
          _id:{date:"$dateRange",make:"$make"},
          count:{$sum:{$cond:[{$eq:["$dateRange","$saleDate"]},1,0]}}
       }},
       {$group:{
          _id:"$_id.date",
          total:{$sum:"$count"},
          byBrand:{$push:{k:"$_id.make",v:{$sum:"$count"}}}
       }},
       {$sort:{_id:1}},
       {$project:{
          _id:0,
          saleDate:"$_id",
          totalSold:"$total",
          byBrand:{$arrayToObject:{$filter:{input:"$byBrand",cond:"$$this.v"}}}
       }}
    ])
    

    相同的结果:

    { "saleDate" : ISODate("2013-04-08T00:00:00Z"), "totalSold" : 0, "byBrand" : { "Honda" : 0, "Toyota" : 0, "Nissan" : 0 } }
    { "saleDate" : ISODate("2013-04-09T00:00:00Z"), "totalSold" : 1, "byBrand" : { "Honda" : 0, "Nissan" : 0, "Toyota" : 1 } }
    { "saleDate" : ISODate("2013-04-10T00:00:00Z"), "totalSold" : 6, "byBrand" : { "Honda" : 0, "Toyota" : 4, "Nissan" : 2 } }
    { "saleDate" : ISODate("2013-04-11T00:00:00Z"), "totalSold" : 1, "byBrand" : { "Toyota" : 1, "Honda" : 0, "Nissan" : 0 } }
    { "saleDate" : ISODate("2013-04-12T00:00:00Z"), "totalSold" : 0, "byBrand" : { "Toyota" : 0, "Nissan" : 0, "Honda" : 0 } }
    { "saleDate" : ISODate("2013-04-13T00:00:00Z"), "totalSold" : 2, "byBrand" : { "Honda" : 1, "Toyota" : 1, "Nissan" : 0 } }
    { "saleDate" : ISODate("2013-04-14T00:00:00Z"), "totalSold" : 0, "byBrand" : { "Toyota" : 0, "Honda" : 0, "Nissan" : 0 } }
    

    基于2.6的原始答案:

    您可能需要查看我的博客文章,了解如何在Aggregation Framework _745757中处理各种日期操作 .

    您可以做的是使用 $project 阶段将日期截断为每日解析,然后对整个数据集(或仅部分)进行聚合,并按日期和制作进行聚合 .

    根据您的样本数据,假设您想知道今年按日期销售的汽车数量:

    match={"$match" : {
                   "saleDate" : { "$gt" : new Date(2013,0,1) }
          }
    };
    
    proj1={"$project" : {
            "_id" : 0,
            "saleDate" : 1,
            "make" : 1,
            "h" : {
                "$hour" : "$saleDate"
            },
            "m" : {
                "$minute" : "$saleDate"
            },
            "s" : {
                "$second" : "$saleDate"
            },
            "ml" : {
                "$millisecond" : "$saleDate"
            }
        }
    };
    
    proj2={"$project" : {
            "_id" : 0,
            "make" : 1,
            "saleDate" : {
                "$subtract" : [
                    "$saleDate",
                    {
                        "$add" : [
                            "$ml",
                            {
                                "$multiply" : [
                                    "$s",
                                    1000
                                ]
                            },
                            {
                                "$multiply" : [
                                    "$m",
                                    60,
                                    1000
                                ]
                            },
                            {
                                "$multiply" : [
                                    "$h",
                                    60,
                                    60,
                                    1000
                                ]
                            }
                        ]
                    }
                ]
            }
        }
    };
    
    group={"$group" : {
            "_id" : {
                "m" : "$make",
                "d" : "$saleDate"
            },
            "count" : {
                "$sum" : 1
            }
        }
    };
    

    现在运行聚合为您提供:

    db.inventory.aggregate(match, proj1, proj2, group)
    {
        "result" : [
            {
                "_id" : {
                    "m" : "Toyota",
                    "d" : ISODate("2013-04-10T00:00:00Z")
                },
                "count" : 4
            },
            {
                "_id" : {
                    "m" : "Toyota",
                    "d" : ISODate("2013-04-09T00:00:00Z")
                },
                "count" : 1
            },
            {
                "_id" : {
                    "m" : "Nissan",
                    "d" : ISODate("2013-04-10T00:00:00Z")
                },
                "count" : 2
            }
        ],
        "ok" : 1
    }
    

    您可以添加另一个{$ project}阶段以完成输出,并且您可以添加{$ sort}步骤,但基本上对于每个日期,对于每个日期,您可以计算出已售出的数量 .

  • 43

    我喜欢user1083621的答案,但该方法在使用该字段的后续操作中会产生一些限制 - 因为您不能在(例如)下一个聚合管道阶段中将其用作日期字段 . 你既不能比较也不能使用任何date aggregation operations,并且在聚合后你会因为通过灌浆阶段保留它而遇到一些困难 . 毕竟,有时候你只想在一天的开始时操纵,而不是随意的一天 . 所以这是我的方法:

    {'$project': {
        'start_of_day': {'$subtract': [
            '$date',
            {'$add': [
                {'$multiply': [{'$hour': '$date'}, 3600000]},
                {'$multiply': [{'$minute': '$date'}, 60000]},
                {'$multiply': [{'$second': '$date'}, 1000]},
                {'$millisecond': '$date'}
            ]}
        ]},
    }}
    

    它给你这个:

    {
        "start_of_day" : ISODate("2015-12-03T00:00:00.000Z")
    },
    {
        "start_of_day" : ISODate("2015-12-04T00:00:00.000Z")
    }
    

    不能说它是否比user1083621的方法更快 .

相关问题