将$ geoNear与另一个集合结合

我有2个collections, restomeal (每个餐文件有它所属的resto id)。 我想要取得至less有一顿饭的附近的restos 。 现在,我可以去附近的餐馆,但我怎样才能确保他们至less有一顿饭?

 restoModel.aggregate([{ "$geoNear": { "near": { "type": "Point", "coordinates": coordinates }, "minDistance": 0, "maxDistance": 1000, "distanceField": "distance", "spherical": true, "limit": 10 // fetch 10 restos at a time } }]); 

示例resto文档:

 { _id: "100", location: { coordinates: [ -63, 42 ], type: "Point" }, name: "Burger King" } 

Sample meal doc:

 { resto_id: "100", // restaurant that this meal belongs to name: "Fried Chicken", price: 12.99 } 

我可以创build一个pipe道,获取10个餐厅,每个餐厅都与其相关的餐食文件join,并删除没有用餐的餐厅。 但是如果所有的文件都没有用餐,那么一个文件就可以返回0个文件。 我怎样才能确保它继续search,直到有10餐有恢复?

这实际上有几个考虑的方法,这些方法有其自身的好处或相关的缺陷。

embedded

最简单最简单的方法就是将“菜单”和“计数”embedded到餐馆的母文档中。

这实际上也是相当合理的,因为你似乎被困在关系build模术语的思考中,其中MongoDB不是一个RDBMS,也不是它应该作为一个整体使用。 相反,我们发挥了MongoDB可以做的事情的优势。

结构会是这样的:

 { _id: "100", location: { coordinates: [ -63, 42 ], type: "Point" }, name: "Burger King", menuCount: 1, menu: [ { name: "Fried Chicken", price: 12.99 } ] } 

这实际上是非常简单的查询,实际上我们可以简单地使用常规的$nearSphere因为我们实际上不再需要聚合条件:

 restoModel.find({ "location": { "$nearSphere": { "$geometry": { "type": "Point", "coordinates": coordinates }, "$maxDistance": 1000 } }, "menuCount": { "$gt": 1 } }).skip(0).limit(10) 

简单而有效。 这实际上正是为什么你应该使用MongoDB,因为“相关”数据已经embedded在父项中。 这当然有“权衡”,但最大的优点是速度和效率。

在父项中维护菜单项以及当前的计数也很简单,因为当添加新项目时,我们可以简单地“增加”计数:

 restoModel.update( { "_id": id, "menu.name": { "$ne": "Pizza" } }, { "$push": { "menu": { "name": "Pizza", "price": 19.99 } }, "$inc": { "menuCount": 1 } } ) 

它将新项目添加到尚不存在的位置,并增加菜单项的数量,所有操作都在一个primefaces操作中,这是embedded关系的原因之一,即更新对父项和子项都同时生效。

这真的是你应该去的。 当然,实际上可以embedded的内容是有限的,但这只是一个“菜单”,与我们可以定义的其他types的关系相比,它的大小当然相对较小。

MongoDB的Elliot实际上把“战争与和平的全部内容作为文本适合在4MB以内 ”的说法写得最好,那时候BSON文档的限制是4MB。 现在它是16MB,并且能够处理任何“菜单”,大多数客户可能会困扰浏览。


与$ lookup汇总

如果你保持一个标准的关系模式,将会有一些问题需要克服。 大多数情况下,与“embedded”的区别在于,由于“菜单”的数据在另一个集合中,因此需要$lookup来“拉”这些数据,然后“计数”多less个数据。

关于“最接近”的查询,与上面的示例不同,我们不能将这些附加约束放在“查询本身”附近 ,这意味着在$geoNear返回的缺省100个结果中,某些项目“可能不“满足额外的限制,你没有select,只能稍后申请”之后“执行$lookup

 restoModel.aggregate([ { "$geoNear": { "near": { "type": "Point", "coordinates": coordinates }, "spherical": true, "limit": 150, "distanceField": "distance", "maxDistance": 1000 }}, { "$lookup": { "from": "menuitems", "localField": "_id", "foreignField": "resto_id", "as": "menu" }}, { "$redact": { "$cond": { "if": { "$gt": [ { "$size": "$menu" }, 0 ] }, "then": "$$KEEP", "else": "$$PRUNE" } }}, { "$limit": 10 } ]) 

因此,您唯一的select是“增加”“可能”回报的数量,然后执行“join”,“计算”和“过滤”的附加stream水线阶段。 同样,将最终的$limit留给它自己的pipe道阶段。

这里指出的一个问题是“分页”结果。 这是因为“下一页”需要实质上“跳过”上一页的结果。 为此,最好实现一个“向前分页”概念,就像这篇文章中所描述的那样: 在MongoDB中实现分页

总体思路是通过$nin “排除”之前“看到”的结果。 这实际上是可以使用$geoNear"query"选项来$geoNear

 restoModel.aggregate([ { "$geoNear": { "near": { "type": "Point", "coordinates": coordinates }, "spherical": true, "limit": 150, "distanceField": "distance", "maxDistance": 1000, "query": { "_id": { "$nin": list_of_seen_ids } } }}, { "$lookup": { "from": "menuitems", "localField": "_id", "foreignField": "resto_id", "as": "menu" }}, { "$redact": { "$cond": { "if": { "$gt": [ { "$size": "$menu" }, 0 ] }, "then": "$$KEEP", "else": "$$PRUNE" } }}, { "$limit": 10 } ]) 

那么至less你不会得到和上一页相同的结果。 但是,它的工作量要多一些,而且比之前的embedded式模型可以做的还要多。


结论

一般情况下,“embedded”作为这个用例的更好的select。 你有一个“小”的数量的相关项目,数据更有意义,实际上是直接与父母关联,因为通常你想菜单和餐馆信息在同一时间。

MongoDB自3.4以来的现代版本确实允许创build“视图” ,但总的前提是基于聚合pipe道的使用。 因此,我们可以在“视图”中“预join”数据,但是由于任何查询操作都有效地拾取要处理的底层聚合stream水线语句,所以$nearSphere等的标准查询操作符不能作为标准查询来应用实际上是“附加”到定义的pipe道。 以类似的方式,你也不能使用$geoNear和“views”。

也许这些约束在将来会发生变化,但是现在这些限制使得这个选项不可行,因为我们无法用更多的关系devise来对“预join”的源进行所需的查询。

所以基本上可以用两种方式来完成,但是为了我的资金,我会在这里build模。