2017-11-11 259 views
8

我有以下查詢:計算的時間差,

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日到現在,並且它正在進行,所以我的教育持續時間是這些時間間隔的總和。

回答

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 
    } 
} 
> 
0

那麼你可能只是簡單地使用現有的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個月。這是積極的或消極的,然後相應調整。與年份相同,基於月份。 –

0

您可以通過使用客戶端的處理與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(...