Mar*_*ria 5 javascript mongoose mongodb node.js aggregation-framework
我有 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
}
}]);
Run Code Online (Sandbox Code Playgroud)
示例恢复文档:
{
_id: "100",
location: { coordinates: [ -63, 42 ], type: "Point" },
name: "Burger King"
}
Run Code Online (Sandbox Code Playgroud)
膳食文件样本:
{
resto_id: "100", // restaurant that this meal belongs to
name: "Fried Chicken",
price: 12.99
}
Run Code Online (Sandbox Code Playgroud)
我可以创建一个管道,获取 10 家餐厅,每家餐厅都加入了相关的用餐文档,并删除不提供餐食的餐厅。但是,如果所有文档都没有吃饭,则一次获取可能会返回 0 个文档。我如何确保它继续搜索,直到返回 10 个用餐餐厅?
这实际上有几种方法需要考虑,这些方法都有各自的好处或相关的陷阱。
最干净、最简单的方法就是将“菜单”和“计数”实际嵌入餐厅的父文档中。
这实际上也是相当合理的,因为您似乎陷入了关系建模术语的思考中,其中 MongoDB 不是 RDBMS,而且通常也不“应该”将其用作 RDBMS。相反,我们发挥 MongoDB 的优势。
那么结构将是这样的:
{
_id: "100",
location: { coordinates: [ -63, 42 ], type: "Point" },
name: "Burger King",
menuCount: 1,
menu: [
{
name: "Fried Chicken",
price: 12.99
}
]
}
Run Code Online (Sandbox Code Playgroud)
这实际上查询起来非常简单,事实上我们可以简单地使用常规应用,$nearSphere因为我们实际上不再需要聚合条件:
restoModel.find({
"location": {
"$nearSphere": {
"$geometry": {
"type": "Point",
"coordinates": coordinates
},
"$maxDistance": 1000
}
},
"menuCount": { "$gt": 1 }
}).skip(0).limit(10)
Run Code Online (Sandbox Code Playgroud)
简单有效。事实上,这正是您应该使用 MongoDB 的原因,因为“相关”数据已经嵌入到父项中。这当然有“权衡”,但最大的优势在于速度和效率。
维护父项中的菜单项以及当前计数也很简单,因为我们可以在添加新项时简单地“增加”计数:
restoModel.update(
{ "_id": id, "menu.name": { "$ne": "Pizza" } },
{
"$push": { "menu": { "name": "Pizza", "price": 19.99 } },
"$inc": { "menuCount": 1 }
}
)
Run Code Online (Sandbox Code Playgroud)
这会在尚不存在的位置添加新项目,并增加菜单项的数量,所有这些都在一个原子操作中完成,这也是您嵌入更新同时影响父级和子级的关系的另一个原因。
这确实是您应该追求的目标。当然,您实际可以嵌入的内容是有限的,但这只是一个“菜单”,与我们可以定义的其他类型的关系相比,其大小当然相对较小。
MongoDB 的 Elliot 实际上说得最好,他说“《战争与和平》的整个内容文本大小不超过 4MB ”,而当时 BSON 文档的限制是 4MB。现在它的大小为 16MB,足以处理大多数客户可能会费心浏览的任何“菜单”。
如果你坚持标准的关系模式,就会有一些问题需要克服。大多数情况下,与“嵌入”的最大区别在于,由于“菜单”的数据位于另一个集合中,因此您需要$lookup将这些数据“拉入”,然后“计算”有多少个。
对于“最近”查询,与上面的示例不同,我们不能将这些附加约束“放在“最近”查询本身内”,这意味着在返回的默认 100 个结果中$geoNear,某些项目“可能不”满足附加约束,您别无选择,只能在$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 }
])
Run Code Online (Sandbox Code Playgroud)
因此,这里唯一的选择是“增加”“可能”返回的数量,然后执行额外的管道阶段来“加入”、“计算”和“过滤”。还将最终留给$limit它自己的管道阶段。
这里值得注意的问题是结果的“分页”。这是因为“下一页”本质上需要“跳过”前一页的结果。为此,最好实现“前向分页”概念,正如本文中所述:在 MongoDB 中实现分页
一般的想法是“排除”先前“看到”的结果,通过$nin. 这实际上可以使用"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 }
])
Run Code Online (Sandbox Code Playgroud)
那么至少你不会得到与上一页相同的结果。但这需要更多的工作,而且比前面所示的嵌入式模型可以完成的工作要多得多。
一般情况下,“嵌入”是该用例的更好选择。您有“少量”相关项目,并且数据实际上与父项直接关联更有意义,因为通常您同时需要菜单和餐厅信息。
自 3.4 以来的现代 MongoDB 版本确实允许创建“视图”,但一般前提是基于聚合管道的使用。因此,我们可以在“视图”中“预加入”数据,但是,由于任何查询操作都有效地获取底层聚合管道语句进行处理,因此$nearSphere无法应用标准查询运算符等,因为标准查询实际上是“附加”到定义的管道。以类似的方式,您也不能$geoNear与“视图”一起使用。
也许约束将来会改变,但现在的限制使得这作为一种选择不可行,因为我们无法使用更相关的设计对“预连接”源执行所需的查询。
因此,您基本上可以通过所提供的两种方式中的任何一种来完成此操作,但为了我的钱,我将建模为嵌入此处。
| 归档时间: |
|
| 查看次数: |
1105 次 |
| 最近记录: |