计算的时间差,

问题描述:

我有以下查询:计算的时间差,

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年,3个月(这意味着该用户共有2年和3个月的课程时间) 。 我想要的是得到教育数组中每个子文档的日期差异,并将其相加。假设我的样本数据为Java,课程时间为6个月,22天,PHP课程时间为1年,6个月和22天,最后一个是从2017年2月8日到现在,并且它正在进行,所以我的教育持续时间是这些时间间隔的总和。

请尝试此聚集,得到几天,几个月甚至几年的时间差,添加了多个$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 
    } 
} 
> 

那么你可能只是简单地使用现有的date aggregation operator S作为反对使用数学对“天”的转换,你目前有:

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 ] } 
    }} 
]) 

这是“之类的”近似的“天”和“几个月”没有必要的操作是闰年的“确定”,但是它会让你获得应该“足够”的结果,用于大多数目的。

你甚至可以做到这一点没有$unwind只要你的MongoDB的版本是3.2或更高:

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

所以主要数学可以真正在“减少”数组的一部分中完成,然后每个总数可以由多年来的一般“因数”和任何来自任何数据的“模数”或“余数”在数月和数天内超支。

本质返回:

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

+0

您的查询'计算的年,月,day'分开,我想要的是像'1年2个月和10个一天,不像'1年,14个月,396天' – jones

+0

@jones这不会发生。您可以使用生成的值形成一个“字符串”,但这只是将值连接成一个完整的字符串,这在客户端代码中更有意义。你真的应该让数据库自然地“减少”内容。正如这里所示。并注意我实际上做了什么,因为我**实际上相应地调整了所有年份,月份和日期。 –

+0

@jones认为我误解了你的评论。再看看,我的确在调整每个部分。这只是因为这里的数学与你所设想的不同。我从未达到14个月。这是积极的或消极的,然后相应调整。与年份相同,基于月份。 –

您可以通过使用客户端的处理与moment js库简化代码。

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

使用reduce可以添加跨所有数组元素的时间差,然后加上瞬间持续时间以输出年/月/日的时间。

它解决了两个问题:

  1. 为您提供了多年的月份和日子两个日期之间准确的区别。
  2. 提供您期望的格式。

例如:

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(...