2017-07-25 59 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这可能是可行的,在那里你可以有效地将数据'上插'到结果集合中。这将需要与流星做一些调整来做“适当的”并行执行,但这是可能的。上面的代码应该直接与流星一起工作。 –