计算年,月,日的日期差异

时间:2017-11-11 09:23:23

标签: mongodb mongoose aggregation-framework

我有以下查询:

db.getCollection('user').aggregate([
   {$unwind: "$education"},
   {$project: {
      duration: {"$divide":[{$subtract: ['$education.to', '$education.from'] }, 1000 * 60 * 60 * 24 * 365]}
   }},
   {$group: {
     _id: '$_id',
     "duration": {$sum: '$duration'}  
   }}]
])

以上查询结果为:

{
    "_id" : ObjectId("59fabb20d7905ef056f55ac1"),
    "duration" : 2.34794520547945
}

/* 2 */
{
    "_id" : ObjectId("59fab630203f02f035301fc3"),
    "duration" : 2.51232876712329
}

但我想要做的是以year + month + day格式获取其持续时间,例如:2 y, 3 m, 20 d。 另外一点,如果to字段上的课程为空,另一个字段为isGoingOn: true,那么我应该使用当前日期而不是to字段来计算持续时间。 用户有一系列课程子文档

education: [
   {
      "courseName": "Java",
      "from" : ISODate("2010-12-08T00:00:00.000Z"),
      "to" : ISODate("2011-05-31T00:00:00.000Z"), 
      "isGoingOn": false
   },
   {
      "courseName": "PHP",
      "from" : ISODate("2013-12-08T00:00:00.000Z"),
      "to" : ISODate("2015-05-31T00:00:00.000Z"), 
      "isGoingOn": false
   },
   {
      "courseName": "Mysql",
      "from" : ISODate("2017-02-08T00:00:00.000Z"),
      "to" : null, 
      "isGoingOn": true
   }
]

另一点是:该日期在一个子文档中可能不连续到另一个子文档。用户可能有1年的课程,然后在两年后,他/她开始他/她的下一个课程1年和3个月(这意味着该用户总共有2年和3个月的课程持续时间) 。 我想要的是在educations数组中获取每个子文档的日期差异,并将它们相加。假设我的样本数据Java课程持续时间为6个月,22天,PHP课程持续时间为1年,6个月和22天,最后一个课程为2017年2月8日至今,它正在发生,所以我的教育持续时间是这些间隔的总和。

3 个答案:

答案 0 :(得分:3)

请尝试此聚合以获取日期,月份和年份的日期差异,添加多个$addFields阶段计算并减少日期差异,月份范围不下溢,此处假设为1个月= 30天

管道

db.edu.aggregate(
    [
        {
            $addFields : {
                trainingPeriod : {
                    $map : {
                        input : "$education",
                        as : "t",
                        in : {
                            year: {$subtract: [{$year : {$ifNull : ["$$t.to", new Date()]}}, {$year : "$$t.from"}]},
                            month: {$subtract: [{$month : {$ifNull : ["$$t.to", new Date()]}}, {$month : "$$t.from"}]},
                            dayOfMonth: {$subtract: [{$dayOfMonth : {$ifNull : ["$$t.to", new Date()]}}, {$dayOfMonth : "$$t.from"}]}
                        }
                    }
                }
            }
        },
        {
            $addFields : {
                trainingPeriod : {
                    $map : {
                        input : "$trainingPeriod",
                        as : "d",
                        in : {
                            year: "$$d.year",
                            month: {$cond : [{$lt : ["$$d.dayOfMonth", 0]}, {$subtract : ["$$d.month", 1]}, "$$d.month" ]},
                            day: {$cond : [{$lt : ["$$d.dayOfMonth", 0]}, {$add : [30, "$$d.dayOfMonth"]}, "$$d.dayOfMonth" ]}
                        }
                    }
                }
            }
        },
        {
            $addFields : {
                trainingPeriod : {
                    $map : {
                        input : "$trainingPeriod",
                        as : "d",
                        in : {
                            year: {$cond : [{$lt : ["$$d.month", 0]}, {$subtract : ["$$d.year", 1]}, "$$d.year" ]},
                            month: {$cond : [{$lt : ["$$d.month", 0]}, {$add : [12, "$$d.month"]}, "$$d.month" ]},
                            day: "$$d.day"
                        }
                    }
                }
            }
        },
        {
            $addFields : {
                total : {
                    $reduce : {
                        input : "$trainingPeriod",
                        initialValue : {year : 0, month : 0, day : 0},
                        in : {
                            year: {$add : ["$$this.year", "$$value.year"]},
                            month: {$add : ["$$this.month", "$$value.month"]},
                            day: {$add : ["$$this.day", "$$value.day"]}
                        }
                    }
                }
            }
        },
        {
            $addFields : {
                total : {
                    year : "$total.year",
                    month : {$add : ["$total.month", {$floor : {$divide : ["$total.day", 30]}}]},
                    day : {$mod : ["$total.day", 30]}
                }
            }
        },
        {
            $addFields : {
                total : {
                    year : {$add : ["$total.year", {$floor : {$divide : ["$total.month", 12]}}]},
                    month : {$mod : ["$total.month", 12]},
                    day : "$total.day"
                }
            }
        }
    ]
).pretty()

结果

{
    "_id" : ObjectId("5a895d4721cbd77dfe857f95"),
    "education" : [
        {
            "courseName" : "Java",
            "from" : ISODate("2010-12-08T00:00:00Z"),
            "to" : ISODate("2011-05-31T00:00:00Z"),
            "isGoingOn" : false
        },
        {
            "courseName" : "PHP",
            "from" : ISODate("2013-12-08T00:00:00Z"),
            "to" : ISODate("2015-05-31T00:00:00Z"),
            "isGoingOn" : false
        },
        {
            "courseName" : "Mysql",
            "from" : ISODate("2017-02-08T00:00:00Z"),
            "to" : null,
            "isGoingOn" : true
        }
    ],
    "trainingPeriod" : [
        {
            "year" : 0,
            "month" : 5,
            "day" : 23
        },
        {
            "year" : 1,
            "month" : 5,
            "day" : 23
        },
        {
            "year" : 1,
            "month" : 0,
            "day" : 10
        }
    ],
    "total" : {
        "year" : 2,
        "month" : 11,
        "day" : 26
    }
}
> 

答案 1 :(得分:0)

那么你可以简单地使用现有的date aggregation operator s而不是使用数学转换为" days"你现在有:

db.getCollection('user').aggregate([
  { "$unwind": "$education" },
  { "$group": {
    "_id": "$_id",
    "years": {
      "$sum": {
        "$subtract": [
          { "$subtract": [
            { "$year": { "$ifNull": [ "$education.to", new Date() ] } },
            { "$year": "$education.from" }
          ]},
          { "$cond": {
            "if": {
              "$gt": [
                { "$month": { "$ifNull": [ "$education.to", new Date() ] } },
                { "$month": "$education.from" }
              ]
            },
            "then": 0,
            "else": 1
          }}
        ]
      }
    },
    "months": {
      "$sum": {
        "$add": [
          { "$subtract": [
            { "$month": { "$ifNull": [ "$education.to", new Date() ] } },
            { "$month": "$education.from" }
          ]},
          { "$cond": {
            "if": {
              "$gt": [
                { "$month": { "$ifNull": ["$education.to", new Date() ] } },
                { "$month": "$education.from" }
              ]
            },
            "then": 0,
            "else": 12
          }}
        ]
      }
    },
    "days": {
      "$sum": {
        "$add": [
          { "$subtract": [
            { "$dayOfYear": { "$ifNull": [ "$education.to", new Date() ] } },
            { "$dayOfYear": "$education.from" }
          ]},
          { "$cond": {
            "if": {
              "$gt": [
                { "$month": { "$ifNull": [ "$education.to", new Date() ] } },
                { "$month": "$education.from" }
              ]
            },
            "then": 0,
            "else": 365
          }}
        ]
      }
    }
  }},
  { "$project": {
    "years": {
      "$add": [
        "$years",
        { "$add": [
          { "$floor": { "$divide": [ "$months", 12 ] } },
          { "$floor": { "$divide": [ "$days", 365 ] } }
        ]}
      ]
    },
    "months": {
      "$mod": [
        { "$add": [
          "$months",
          { "$floor": {
            "$multiply": [
              { "$divide": [ "$days", 365 ] },
              12
            ]
          }}
        ]},
        12
      ]
    },
    "days": { "$mod": [ "$days", 365 ] }
  }}
])

它是"有点"在"天"的近似值和"月"没有必要的操作"某些"闰年,但它会得到你应该"足够接近"用于大多数目的。

只要您的MongoDB版本为3.2或更高版本,您甚至可以在没有$unwind的情况下执行此操作:

db.getCollection('user').aggregate([
  { "$addFields": {
    "duration": {
      "$let": {
        "vars": {
          "edu": {
            "$map": {
              "input": "$education",
              "as": "e",
              "in": {
                "$let": {
                  "vars": { "toDate": { "$ifNull": ["$$e.to", new Date()] } },
                  "in": {
                    "years": {
                      "$subtract": [
                        { "$subtract": [
                          { "$year": "$$toDate" },
                          { "$year": "$$e.from" }   
                        ]},
                        { "$cond": {
                          "if": { "$gt": [{ "$month": "$$toDate" },{ "$month": "$$e.from" }] },
                          "then": 0,
                          "else": 1
                        }}
                      ]
                    },
                    "months": {
                      "$add": [
                        { "$subtract": [
                          { "$ifNull": [{ "$month": "$$toDate" }, new Date() ] },
                          { "$month": "$$e.from" }
                        ]},
                        { "$cond": {
                          "if": { "$gt": [{ "$month": "$$toDate" },{ "$month": "$$e.from" }] },
                          "then": 0,
                          "else": 12
                        }}
                      ]
                    },
                    "days": {
                      "$add": [
                        { "$subtract": [
                          { "$ifNull": [{ "$dayOfYear": "$$toDate" }, new Date() ] },
                          { "$dayOfYear": "$$e.from" }
                        ]},
                        { "$cond": {
                          "if": { "$gt": [{ "$month": "$$toDate" },{ "$month": "$$e.from" }] },
                          "then": 0,
                          "else": 365
                        }}
                      ]
                    }
                  }
                }
              }
            }    
          }
        },
        "in": {
          "$let": {
            "vars": {
              "years": { "$sum": "$$edu.years" },
              "months": { "$sum": "$$edu.months" },
              "days": { "$sum": "$$edu.days" }    
            },
            "in": {
              "years": {
                "$add": [
                  "$$years",
                  { "$add": [
                    { "$floor": { "$divide": [ "$$months", 12 ] } },
                    { "$floor": { "$divide": [ "$$days", 365 ] } }
                  ]}
                ]
              },
              "months": {
                "$mod": [
                  { "$add": [
                    "$$months",
                    { "$floor": {
                      "$multiply": [
                        { "$divide": [ "$$days", 365 ] },
                        12
                      ]
                    }}
                  ]},
                  12
                ]
              },
              "days": { "$mod": [ "$$days", 365 ] }
            }
          }
        }
      }
    }
  }}
]) 

这是因为从MongoDB 3.4开始,您可以$sum直接使用$addFields$project等阶段的数组或任何表达式列表,$map可以应用那些相同的"日期聚合运算符"对每个数组元素的表达式代替首先执行$unwind

所以主要的数学运算真的可以在"减少"的一部分完成。数组,然后每个总数可以由一般"除数"多年来," modulo"或"余数"来自几个月和几天的任何超支。

基本上返回:

{
    "_id" : ObjectId("5a07688e98e4471d8aa87940"),
    "education" : [ 
        {
            "courseName" : "Java",
            "from" : ISODate("2010-12-08T00:00:00.000Z"),
            "to" : ISODate("2011-05-31T00:00:00.000Z"),
            "isGoingOn" : false
        }, 
        {
            "courseName" : "PHP",
            "from" : ISODate("2013-12-08T00:00:00.000Z"),
            "to" : ISODate("2015-05-31T00:00:00.000Z"),
            "isGoingOn" : false
        }, 
        {
            "courseName" : "Mysql",
            "from" : ISODate("2017-02-08T00:00:00.000Z"),
            "to" : null,
            "isGoingOn" : true
        }
    ],
    "duration" : {
        "years" : 3.0,
        "months" : 3.0,
        "days" : 259.0
    }
}

鉴于2017年11月11日

答案 2 :(得分:0)

您可以使用moment js库进行客户端处理来简化代码。

所有日期时间数学都由时刻js库处理。使用duration计算缩短的时间diff

使用reduce在所有数组元素之间添加时间差异,然后按片段持续时间输出以年/月/天为单位的时间。

它解决了两个问题:

  1. 为您提供两个日期之间的月份和日期的准确差异。
  2. 给出您期望的格式。
  3. 例如:

    var education = [
       {
          "courseName": "Java",
          "from" : new Date("2010-12-08T00:00:00.000Z"),
          "to" : new Date("2011-05-31T00:00:00.000Z"), 
          "isGoingOn": false
       },
       {
          "courseName": "PHP",
          "from" : new Date("2013-12-08T00:00:00.000Z"),
          "to" : new Date("2015-05-31T00:00:00.000Z"), 
          "isGoingOn": false
       },
       {
          "courseName": "Mysql",
          "from" : new Date("2017-02-08T00:00:00.000Z"),
          "to" : null, 
          "isGoingOn": true
       }
    ];
    
    var reducedDiff = education.reduce(function(prevVal, elem) {
        if(elem.isGoingOn) elem.to = new Date();
        var diffDuration = moment(elem.to).diff(moment(elem.from));
        return prevVal + diffDuration;
    }, 0);
    
    var duration = moment.duration(reducedDiff);
    
    alert(duration.years() +" y, " + duration.months() + " m, " +  duration.days() + " d " );
    var durationstr =  duration.years() +" y, " + duration.months() + " m, " +  duration.days() + " d ";
    

    MongoDb整合:

    var reducedDiff = db.getCollection('user').find({},{education:1}).reduce(function(...