我有2个集合,resto
和meal
(每个膳食文档都有它所属的resto id)。我想要获取附近有至少一餐的餐厅。现在,我可以去附近的餐馆,但我如何结合,确保他们至少吃了一顿饭?
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 doc:
{
_id: "100",
location: { coordinates: [ -63, 42 ], type: "Point" },
name: "Burger King"
}
样品膳食文件:
{
resto_id: "100", // restaurant that this meal belongs to
name: "Fried Chicken",
price: 12.99
}
我可以创建一个管道,获取10家餐厅,每家餐馆都附有相关的膳食文件,并删除没有用餐的餐馆。但是,如果所有菜单都没有用餐,则单个提取可以返回0个文档。我如何确保它继续搜索,直到返回10餐餐厅?
答案 0 :(得分:2)
这实际上有几种方法需要考虑,这些方法有其自身的利益或陷阱。
最干净,最简单的方法就是实际嵌入"菜单"和"计数"而是在餐厅的父文件中。
这实际上也是非常合理的,因为你似乎陷入了关系建模术语的思考,其中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的原因,因为"相关"数据已嵌入父项中。当然还有#34;权衡"对此,但最大的优势在于速度和效率。
维护父项中的菜单项以及当前计数也很简单,因为我们可以简单地“增加”#34;添加新项目时的计数:
restoModel.update(
{ "_id": id, "menu.name": { "$ne": "Pizza" } },
{
"$push": { "menu": { "name": "Pizza", "price": 19.99 } },
"$inc": { "menuCount": 1 }
}
)
将新项目添加到尚不存在的位置并增加菜单项的数量,所有这些都在一个原子操作中,这是为什么您在同一时间内嵌入关系,同时更新对父项和子项都有影响的另一个原因时间。
这真的是你应该去的。当然你可以嵌入的内容有限,但这只是一个菜单"当然,与我们可以定义的其他种类关系相比,它的规模相对较小。
MongoDB的Elliot实际上通过陈述"战争与和平的整个内容在文本中适合4MB "并且那是一次当BSON文件的限制为4MB时。现在它只有16MB,而且能够处理任何"菜单"大多数客户可能会被打扰浏览。
在保持标准关系模式的情况下,需要克服一些问题。这里的主要区别在于"嵌入"是因为"菜单的数据"在另一个集合中,那么你需要$lookup
以便"拉"那些在,然后"计数"有多少。
关于"最近的"查询,与上面的示例不同,我们不能将这些额外的约束"放在' near'查询本身" ,这意味着在$geoNear
返回的默认100个结果中,某些项目"可能不会"满足额外的约束,你别无选择,只能在以后申请,"" $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 }
])
因此,你唯一的选择是"增加"可能的数量"返回,然后执行其他管道阶段到"加入","计算"和"过滤"。也将最终$limit
留给它自己的管道阶段。
这里一个值得注意的问题是" paging"结果这是因为"下一页"需要基本上"跳过"前一页的结果。为此,最好实现"前向寻呼"概念,就像这篇文章中描述的那样:Implementing Pagination In MongoDB
一般的想法是"排除"事先"见过"结果,通过$nin
。这实际上可以使用$geoNear
的"query"
选项完成:
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 }
])
然后至少你没有得到与上一页相同的结果。但它的工作量要多一些,而且工作量远远超过前面所示的嵌入式模型。
一般情况导致"嵌入"作为此用例的更好选择。你有一个"小"相关项目的数量,实际上与父母直接相关的数据更有意义,因为通常你想要菜单和餐馆信息。
自3.4以来MongoDB的现代版本允许"view" to be created,但一般前提是基于聚合管道的使用。因此我们可以预先加入"但是,由于任何查询操作都有效地获取要处理的基础聚合管道语句,因此无法应用$nearSphere
等标准查询运算符,因为标准查询是实际上"追加"到定义的管道。以类似的方式,您也无法使用$geoNear
和#34;视图"。
也许这些限制将来会发生变化,但是现在这些限制使得这个选项不可行,因为我们无法在"预先加入"具有更多关系设计的来源。
所以你基本上可以用两种方式中的任何一种来做,但是为了我的钱,我会在这里模拟嵌入。