2017-07-25 57 views
0

我已經文檔存儲到MongoDB的是這樣的:集團通過一天多日期字段

{ 
    "_id" : "XBpNKbdGSgGfnC2MJ", 
    "po" : 72134185, 
    "machine" : 40940, 
    "location" : "02A01", 
    "inDate" : ISODate("2017-07-19T06:10:13.059Z"), 
    "requestDate" : ISODate("2017-07-19T06:17:04.901Z"), 
    "outDate" : ISODate("2017-07-19T06:30:34Z") 
} 

而且我想給的總和,按天,銦酸過時的。

我可以通過inDate一天檢索雙方的文件的總和,另一方面,文件的總和由outDate,但我想每個的總和。

目前,我用這個管道:

 $group: { 
     _id: { 
      yearA: { $year: '$inDate' }, 
      monthA: { $month: '$inDate' }, 
      dayA: { $dayOfMonth: '$inDate' }, 
     }, 
     count: { $sum: 1 }, 
     }, 

,我給:

{ "_id" : { "year" : 2017, "month" : 7, "day" : 24 }, "count" : 1 } 
{ "_id" : { "year" : 2017, "month" : 7, "day" : 21 }, "count" : 11 } 
{ "_id" : { "year" : 2017, "month" : 7, "day" : 19 }, "count" : 20 } 

但我想,如果有可能

{ "_id" : { "year" : 2017, "month" : 7, "day" : 24 }, "countIn" : 1, "countOut" : 4 } 
{ "_id" : { "year" : 2017, "month" : 7, "day" : 21 }, "countIn" : 11, "countOut" : 23 } 
{ "_id" : { "year" : 2017, "month" : 7, "day" : 19 }, "countIn" : 20, "countOut" : 18 } 

任何想法? 非常感謝:-)

+0

這裏的最佳做法是單獨運行每個聚合,然後在後處理中「結合」結果。運行「並行」進程並基本上「共同」輸出密鑰是相當簡單和常見的做法。運行「並行」是您的更好選擇,根據語言選擇和環境的不同處理方式以及整體結果的大小。因此理想情況下使用nodejs或其他可以在「異步」問題中並行執行查詢的內容,而不是分別單獨封鎖查詢。 –

回答

-1

Riiiight。我想出了以下查詢。誠然,我已經看到了我的生活變得更加簡單和更好的人,但它肯定能夠完成任務:

db.getCollection('test').aggregate 
(
    { 
    $facet: // split aggregation into two pipelines 
    { 
     "in": [ 
     { "$match": { "inDate": { "$ne": null } } }, // get rid of null values 
     { $group: { "_id": { "y": { "$year": "$inDate" }, "m": { "$month": "$inDate" }, "d": { "$dayOfMonth": "$inDate" } }, "cIn": { $sum : 1 } } }, // compute sum per inDate 
     ], 
     "out": [ 
     { "$match": { "outDate": { "$ne": null } } }, // get rid of null values 
     { $group: { "_id": { "y": { "$year": "$outDate" }, "m": { "$month": "$outDate" }, "d": { "$dayOfMonth": "$outDate" } }, "cOut": { $sum : 1 } } }, // compute sum per outDate 
     ] 
    } 
    }, 
    { $project: { "result": { $setUnion: [ "$in", "$out" ] } } }, // merge results into new array 
    { $unwind: "$result" }, // unwind array into individual documents 
    { $replaceRoot: { newRoot: "$result" } }, // get rid of the additional field level 
    { $group: { _id: { year: "$_id.y", "month": "$_id.m", "day": "$_id.d" }, "countIn": { $sum: "$cIn" }, "countOut": { $sum: "$cOut" } } } // group into final result 
) 

按照一貫的MongoDB的集合,你可以得到什麼的通過簡單地減少投影事情的想法階段一步一步從查詢結束開始。

編輯:

正如你可以在評論見下面有一個有點繞文件大小限制的討論,這種解決方案的普遍適用性。

那麼我們來仔細看看這些方面,並且我們還將基於0​​的解決方案與基於$map的解決方案(由@NeilLunn建議以避免潛在的文檔大小問題)的性能進行比較。

我創建2元即有分配給該「銦酸」和「過時」現場隨機日期測試記錄:

{ 
    "_id" : ObjectId("597857e0fa37b3f66959571a"), 
    "inDate" : ISODate("2016-07-29T22:00:00.000Z"), 
    "outDate" : ISODate("1988-07-14T22:00:00.000Z") 
} 

覆蓋的數據範圍爲01.01.1970一路01.01。 2050年,共有29220個不同的日子。考慮到在這段時間範圍內的200萬條測試記錄的隨機分佈,這兩個查詢都可以返回完整的29220個可能的結果(兩者都做到了這一點)。

然後,我跑到剛重新啓動我的單個MongoDB實例後兩個詢問五次,以毫秒爲單位,結果我得到了這個樣子:

$facet:5663,5400,5380,5460,5520

$map: 9648,9134,9058,9085,9132

我也是measured the size由方面階段返回的單個文檔是3。19MB與MongoDB文檔大小限制相差太遠(本文編寫時爲16MB),然而,這隻適用於結果文檔,並且在流水線處理期間不會成爲問題。

底線:如果你想性能,使用這裏提出的解決方案。但是,要小心文檔大小限制,特別是如果您的用例不是上述問題中描述的確切用例(例如,當您需要收集更多/更大的數據時)。另外,我不知道,如果在分片情況這兩種解決方案仍暴露相同的性能特點...

+0

['$ facet'(https://docs.mongodb.com/manual/reference/operator/aggregation/facet/)表示你是把** ** ALL結果爲**單一的文件**。在幾乎每一個真實世界的情況下,這意味着你會打破BSON限制。這只是真正的預期用途是返回已經「減少」的數字。如來自單獨管道過程的「計數」結果。它不被用於在一個進程中「鞋拔」運行多個查詢。如果有人需要「多個查詢」,那麼他們應該將它們作爲「多個查詢」來運行。這是很好的做法。 –

+0

這完全不是我如何閱讀文檔... https://docs.mongodb.com/manual/reference/operator/aggregation/facet/'$ facet'與單個文檔無關 - 它只是允許你爲同一組文檔創建多個聚合流水線。此外,我*在* $ facet階段聚合,因此輸出將很小。哦,我想,@Aurélien可以嘗試一些運行,看看內存/性能如何表現。 – dnickless

+0

你當然是不正確的,也許應該自己嘗試。 '$ facet'實際上產生**一個**文檔作爲流水線輸出。它確實說這個,即使它不是很突出。但文檔頁面上的輸出示例也都顯示**一個**文檔。因此,BSON限制存在問題,任何不能有意「減少」的情況都應避免。所以我的觀點是「強調」這個建議是不好的做法**,不要被遵循。 –

0

您也可以分割文件的來源,通過「類型」基本上每個值組合成項的數組爲「in」和「out」。爲此,您可以簡單地使用$map$cond選擇字段,然後$unwind的數組,然後確定哪些領域通過與$cond檢查「算」了:

collection.aggregate([ 
    { "$project": { 
    "dates": { 
     "$filter": { 
     "input": { 
      "$map": { 
      "input": [ "in", "out" ], 
      "as": "type", 
      "in": { 
       "type": "$$type", 
       "date": { 
       "$cond": { 
        "if": { "$eq": [ "$$type", "in" ] }, 
        "then": "$inDate", 
        "else": "$outDate" 
       } 
       } 
      } 
      } 
     }, 
     "as": "dates", 
     "cond": { "$ne": [ "$$dates.date", null ] } 
     } 
    } 
    }}, 
    { "$unwind": "$dates" }, 
    { "$group": { 
    "_id": { 
     "year": { "$year": "$dates.date" }, 
     "month": { "$month": "$dates.date" }, 
     "day": { "$dayOfMonth": "$dates.date" } 
    }, 
    "countIn": { 
     "$sum": { 
     "$cond": { 
      "if": { "$eq": [ "$dates.type", "in" ] }, 
      "then": 1, 
      "else": 0 
     } 
     } 
    }, 
    "countOut": { 
     "$sum": { 
     "$cond": { 
      "if": { "$eq": [ "$dates.type", "out" ] }, 
      "then": 1, 
      "else": 0 
     } 
     } 
    } 
    }} 
]) 

這是一個安全的方式來做到這一點不無論您發送的數據量多大,都有可能違反BSON限制。

個人而言,我寧願運行爲單獨的進程和「結合」分開彙總結果,但是這將取決於你在運行未在問題中提到的環境。


對於「平行」執行的例子,你可以在流星結構沿着這些線路的地方:

import { Meteor } from 'meteor/meteor'; 
import { Source } from '../imports/source'; 
import { Target } from '../imports/target'; 

Meteor.startup(async() => { 
    // code to run on server at startup 

    await Source.remove({}); 
    await Target.remove({}); 

    console.log('Removed'); 

    Source.insert({ 
    "_id" : "XBpNKbdGSgGfnC2MJ", 
    "po" : 72134185, 
    "machine" : 40940, 
    "location" : "02A01", 
    "inDate" : new Date("2017-07-19T06:10:13.059Z"), 
    "requestDate" : new Date("2017-07-19T06:17:04.901Z"), 
    "outDate" : new Date("2017-07-19T06:30:34Z") 
    }); 

    console.log('Inserted'); 

    await Promise.all(
    ["In","Out"].map(f => new Promise((resolve,reject) => { 
     let cursor = Source.rawCollection().aggregate([ 
     { "$match": { [`${f.toLowerCase()}Date`]: { "$exists": true } } }, 
     { "$group": { 
      "_id": { 
      "year": { "$year": `$${f.toLowerCase()}Date` }, 
      "month": { "$month": `$${f.toLowerCase()}Date` }, 
      "day": { "$dayOfYear": `$${f.toLowerCase()}Date` } 
      }, 
      [`count${f}`]: { "$sum": 1 } 
     }} 
     ]); 

     cursor.on('data', async (data) => { 
     cursor.pause(); 
     data.date = data._id; 
     delete data._id; 
     await Target.upsert(
      { date: data.date }, 
      { "$set": data } 
     ); 
     cursor.resume(); 
     }); 

     cursor.on('end',() => resolve('done')); 
     cursor.on('error', (err) => reject(err)); 
    })) 
); 

    console.log('Mapped'); 

    let targets = await Target.find().fetch(); 
    console.log(targets); 

}); 

在這樣的評語中提到的基本上是要輸出到目標集合:

{ 
     "_id" : "XdPGMkY24AcvTnKq7", 
     "date" : { 
       "year" : 2017, 
       "month" : 7, 
       "day" : 200 
     }, 
     "countIn" : 1, 
     "countOut" : 1 
} 
+0

@無錯誤的錯字糾正。應該是一個明確的錯字。只是在這裏指出正確性。 –

+0

非常感謝,我使用NodeJs和Meteor框架。我將爲結合兩種結果創建後期處理。也許我可以嘗試使用本地收藏到客戶端。 –

+0

@Aurélien這可能是可行的,在那裏你可以有效地將數據'上插'到結果集合中。這將需要與流星做一些調整來做「適當的」並行執行,但這是可能的。上面的代碼應該直接與流星一起工作。 –