在这种情况下,存在一个非常难以证明使用场景的公正案例,因此我认为“搜索旅馆”不是真正适用于“文本”和“地理空间”搜索组合的应用。实际上,“大多数人”会寻找靠近某个位置的东西,甚至更有可能靠近他们想要访问的各个位置作为其主要标准,而其他的“获胜者”很可能更加注重“成本”、“评分”、“品牌”、“设施”,甚至可能是靠近餐馆等方面的接近程度。将“文本搜索”添加到该列表中是一件非常不同的事情,并且在这种特定应用程序中可能没有太多实际用途。尽管如此,这可能值得一些解释,对于为什么这两个概念在这个使用案例中并不真正“契合”的原因,有一些概念需要理解。
修正模式
首先,我想建议“微调”数据模式:
{
"name" : "The Most Amazing Hotel",
"city" : "India",
"location": {
"type": "Point",
"coordinates": [
72.867804,
19.076033
]
}
}
至少提供了“位置”作为有效的GeoJSON对象进行索引,通常您希望使用GeoJSON而不是传统的坐标对,因为它可以打开更多的查询和存储选项,并且距离以米为标准,而不是在全球范围内等效的“弧度”。
为什么它们不能共同工作
所以你的理解基本上是正确的,即您不能同时使用多个特殊索引。首先看一下复合索引的定义:
db.hotels.createIndex({ "name": "text", "location": "2dsphere" })
{
"ok" : 0,
"errmsg" : "索引键模式{name:\"text\",location:\"2dsphere\"}不良:不能为单个索引使用多个索引插件。",
"code" : 67 }
因此,这是无法完成的。即使分别考虑:
db.hotels.createIndex({ "name": "text" })
db.hotels.createIndex({ "location": "2dsphere" })
然后尝试进行查询:
db.hotels.find({
"location": {
"$nearSphere": {
"$geometry": {
"type": "Point",
"coordinates": [
72.867804,
19.076033
]
}
}
},
"$text": { "$search": "Amazing" }
})
错误:命令失败:{
"waitedMS" : NumberLong(0),
"ok" : 0,
"errmsg" : "text and geoNear not allowed in same query",
"code" : 2
} : undefined
这实际上支持了三种不能在复合索引中定义的原因:
正如最初的错误所示,MongoDB中处理这些“特殊”索引的方式需要基本上“分支”到所选索引类型的“特殊”处理程序,而两个处理程序不在同一个位置。
即使使用单独的索引,由于逻辑基本上是“and”条件,MongoDB也无法选择多个索引,因为两个查询子句都需要“特殊”处理,因此实际上必须这样做。而它不能。
即使这在逻辑上是一个$or
条件,你基本上会回到点1,在那里即使应用“索引交集”,这些“特殊”索引的另一个属性也是它们必须在查询操作的“顶层”应用才能允许索引选择。将它们包装在$or
中意味着MongoDB无法这样做,因此不允许。
但你可以“作弊”
因此,每个基本上都必须是独占的,你不能同时使用它们。但是,当然你可以“作弊”,这取决于哪种搜索顺序对你更重要。
先通过“位置”:
db.hotels.aggregate([
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [
72.867804,
19.076033
]
},
"spherical": true,
"maxDistance": 5000,
"distanceField": "distance",
"query": {
"name": /Amazing/
}
}}
])
或者甚至:
db.hotels.find({
"location": {
"$nearSphere": {
"$geometry": {
"type": "Point",
"coordinates": [
72.867804,
19.076033
]
},
"$maxDistance": 5000
}
},
"name": /Amazing/
})
或者首先通过文本搜索:
db.hotels.find({
"$text": { "$search": "Amazing" },
"location": {
"$geoWithin": {
"$centerSphere": [[
72.867804,
19.076033
], 5000 ]
}
}
})
现在您可以使用
.explain()
仔细查看每个方法中的选择选项,以了解发生了什么,但基本情况是各自只选择一个特殊索引来使用。
在第一种情况下,它将使用集合上的地理空间索引作为主要索引,并根据其与首先给定位置的接近程度找到结果,然后通过给定
name
字段的正则表达式参数进行过滤。
在第二种情况下,它将使用“text”索引进行主要选择(因此首先找到“Amazing”),并从这些结果应用地理空间过滤器(不使用索引)与
$geoWithin
,在这种情况下,它执行的基本上是等价于
$near
所做的事情,通过在提供的距离内搜索
点周围的圆以过滤结果。
然而,关键要考虑的是,每种方法返回的结果可能不同。通过首先缩小位置范围,只能检查那些在指定距离内的位置数据,因此距离之外的任何“Amazing”都不会被附加过滤器考虑。
在第二种情况下,由于文本术语是主要搜索,因此将考虑“所有”“Amazing”的结果,并且从最初的文本过滤器允许返回的结果中只能返回那些被允许返回的项目。
这在整体考虑中非常重要,因为两个查询操作(“text”和“geoSpatial”)努力实现非常不同的目标。在“text”案例中,它正在寻找给定术语的“顶级结果”,并且自然地仅以排名顺序返回有限数量的匹配项。这意味着,在应用任何其他过滤条件时,很可能有许多符合第一个条件的项目不符合其他条件。
简而言之,
'并非所有“Amazing”都必须靠近查询点',这意味着在实际限制(例如
100个结果
)和最佳匹配的情况下,这100个结果可能不包含所有“附近”的项目。
此外,
$text
运算符本身实际上并不会以任何方式 "排序" 结果。它的主要目的不仅在于匹配短语,而且还在于
"评分"结果,以将 "最佳" 匹配浮动到顶部。这通常是在查询本身之后完成的,投影值被 "排序" 并可能被 "限制",如上所述。在聚合管道中可能有这样的操作,然后应用第二个过滤器,但是如上所述,这可能会排除其他目的下 "接近" 的结果。
反过来也很可能是真实的(
'还有许多距离该点更远的 "惊人" 事物'),但是随着现实距离限制的加入,这种情况变得不太可能。但是给出的另一个考虑因素是这不是一个
真正 的文本搜索,而只是使用正则表达式来匹配给定术语。
最后一个备注,我总是在这里使用
"Amazing"
作为示例短语,而不是问题中建议使用
"Most"
。这是因为在文本索引中的词干处理方式(以及大多数专用文本搜索产品中的方式)是,特定术语会被
忽略,就像
"and"、"or"、"the",甚至 "in" 一样,因为它们并不真正被认为是短语的
有价值的部分,而这正是文本搜索所做的。
因此,如果确实需要这样做,那么正则表达式实际上比匹配此类术语更好。
总之,这实际上将我们带回到了原始观点,即 "文本" 查询实际上在这里并不适用。其他有用的过滤器通常与真正的 "地理空间" 搜索条件一起使用,并且真正的 "文本搜索" 真的不是最重要的事情之一。
更可能的是人们想要一个位于从他们希望访问的目的地到达的距离的 *"集合交集" 中的位置,或者至少足够接近某些或大多数。然后当然还有其他因素( *"价格"、"服务" 等)作为一般考虑的东西。
以这种方式查找结果并不是一个
"好的解决方案"。如果您认为您确实必须这样做,那么请使用 "欺骗性" 方法之一,或者实际上使用不同的查询,然后使用其他逻辑来合并每组结果。但是,服务器单独执行这项任务真的没有意义,这就是为什么它不会尝试的原因。
因此,我建议首先正确地获取您的地理空间匹配,然后应用其他应该重要的标准进行匹配。但是,我真的不相信 "文本搜索" 真的有效,成为其中之一。如果确实需要,请使用 "欺骗性" 方法。