MongoDB-使用聚合而不是日期范围的常规范围查询是否明智?

时间:2018-09-10 11:55:49

标签: mongodb mongoose aggregation-framework

我正在尝试在MongoDB中执行日期范围查询。假设我要查找在“ 2018年5月”中创建的记录。现在,根据知识,我们可以通过以下方式实现这一目标。

  1. 一种方法是对createdAt进行简单的范围查询。例如 {createdAt: { $gte: ISODate('2018-05-01'), $lte: ISODate('2018-05-31')}}

  2. 另一种方法是聚合 db.collection.aggregate([{ $project: { year: { $year: '$createdAt' }, month: { $month: '$createdAt' }}, { $match: { year: 2018, month: 5}}])

那么,如果可以通过简单的查询获得结果,何时使用聚合?如果以上查询或方法不正确或过旧,请更正。我正在Compass中进行测试。

谢谢

3 个答案:

答案 0 :(得分:1)

您不需要$project阶段(实际上,您编写投影的方式输出文档将只包含投影值和_id字段,无论您是不是想)。因此,剩下的只是聚合查询$match。可以假定查询执行时间是相同的,只是阶段处理要付出较小的代价。另外,我个人希望“正常” find()在内部几乎完全融合到聚合框架中,如果这还没有完全发生的话。保留两种查询数据的方式只是没有道理...

出于清楚的原因,我个人倾向于使用find()版本。

答案 1 :(得分:1)

如果您可以通过简单的查询来做到这一点,只需保持简单即可。 有许多框架可以轻松支持查询,但不能支持聚合。

聚合很难编写和维护。在某些情况下,您确实需要它们(例如在处理数组或子文档时),但如果不需要,请使用find()保留它们。 有关何时需要聚合的示例:

MongoDB - Get latest non-null field value from documents with timestamp

此外,我不确定您的聚合代码是否与find()代码相同。 如果检查正确,则首先使用$ project阶段,然后进行匹配。 这意味着您要投影数据库的所有对象,然后在它们之间进行匹配,因为没有使用$ match开始聚合。 如果您有很多物体,这可能会很慢。

此外,$ match仅在处于第一阶段时才使用索引:

  

将$ match尽可能早地放在聚合管道中。   因为$ match限制了聚合中文档的总数   管道,较早的$ match操作可最大程度地减少处理量   下管道。如果您在$的开头放置$ match   管道,查询可以像其他任何索引一样利用索引   db.collection.find()或db.collection.findOne()。

答案 2 :(得分:1)

根据我的经验,执行基本的find()后不久,您的需求就会发生变化,并需要管道的力量。例如,考虑以下文档:

{ "_id" : 0, "date" : ISODate("2018-01-10T00:00:00Z") }
{ "_id" : 1, "date" : ISODate("2018-01-07T00:00:00Z") }
{ "_id" : 2, "date" : ISODate("2018-01-03T00:00:00Z") }
{ "_id" : 3 }
{ "_id" : 4, "date" : ISODate("2018-01-20T00:00:00Z") }
{ "_id" : 5 }
{ "_id" : 6 }
{ "_id" : 7, "date" : ISODate("2018-01-18T00:00:00Z") }
{ "_id"  :8, "date" : ISODate("2018-01-10T00:00:00Z") }

我们要查找日期<= 2018-01-15的所有地方:

db.foo.aggregate([
{$match: {"date": {$lte: new ISODate("2018-01-15")}} }
                ]);

{ "_id" : 0, "date" : ISODate("2018-01-10T00:00:00Z") }
{ "_id" : 1, "date" : ISODate("2018-01-07T00:00:00Z") }
{ "_id" : 2, "date" : ISODate("2018-01-03T00:00:00Z") }
{ "_id" : 8, "date" : ISODate("2018-01-10T00:00:00Z") }

糟糕!我们也需要空白日期:

db.foo.aggregate([
{$match: {"$or": [ {"date": {$lte: new ISODate("2018-01-15")}}, {"date": {$exists: false}} ] }}
]);

{ "_id" : 0, "date" : ISODate("2018-01-10T00:00:00Z") }
{ "_id" : 1, "date" : ISODate("2018-01-07T00:00:00Z") }
{ "_id" : 2, "date" : ISODate("2018-01-03T00:00:00Z") }
{ "_id" : 3 }
{ "_id" : 5 }
{ "_id" : 6 }
{ "_id" : 8, "date" : ISODate("2018-01-10T00:00:00Z") }

我们希望对它进行排序:

db.foo.aggregate([
{$match: {"$or": [ {"date": {$lte: new ISODate("2018-01-15")}}, {"date": {$exists: false}} ] }}
,{$sort: {"date":1}}
]);

{ "_id" : 3 }
{ "_id" : 5 }
{ "_id" : 6 }
{ "_id" : 2, "date" : ISODate("2018-01-03T00:00:00Z") }
{ "_id" : 1, "date" : ISODate("2018-01-07T00:00:00Z") }
{ "_id" : 0, "date" : ISODate("2018-01-10T00:00:00Z") }
{ "_id" : 8, "date" : ISODate("2018-01-10T00:00:00Z") }

嗯。但是我们希望空格出现在排序列表的末尾。因此,我们用自身覆盖date字段,或者如果覆盖null,则实际上是一个很远的日期,这现在为我们提供了我们想要的顺序:

db.foo.aggregate([
{$match: {"$or": [ {"date": {$lte: new ISODate("2018-01-15")}}, {"date": {$exists: false}} ] }}
,{$addFields: {"date": {$ifNull: [ "$date", new ISODate("3000-01-01")] }}}
,{$sort: {"date":1}}
]);

{ "_id" : 2, "date" : ISODate("2018-01-03T00:00:00Z") }
{ "_id" : 1, "date" : ISODate("2018-01-07T00:00:00Z") }
{ "_id" : 0, "date" : ISODate("2018-01-10T00:00:00Z") }
{ "_id" : 8, "date" : ISODate("2018-01-10T00:00:00Z") }
{ "_id" : 3, "date" : ISODate("3000-01-01T00:00:00Z") }
{ "_id" : 5, "date" : ISODate("3000-01-01T00:00:00Z") }
{ "_id" : 6, "date" : ISODate("3000-01-01T00:00:00Z") }

在此主题上有多种变体,例如执行初始$project以创建公共的非空排序字段,但是将$match放在最前面可以利用索引(如果存在)。当您的文档包含需要查询和操作的数组数据时,agg管道的真正威力便会彰显出来。