假设我想开发一个Android应用程序,允许用户搜索离您最近的酒店。这在当今的应用程序中非常常见,例如AirBnb。
这是我正在使用的数据集:
{
"name" : "The Most Amazing Hotel",
"city" : "India",
"type": "Point"
"coord": [
-56.16082,
61.15392
]
}
{
"name" : "The Most Incredible Hotel",
"city" : "India",
"type": "Point"
"coord": [
-56.56285,
61.34590
]
}
{
"name" : "The Fantastic GuestHouse",
"city" : "India",
"type": "Point"
"coord": [
-56.47085,
61.11357
]
}
现在,我想在name
字段上创建一个文本索引,以便按名称搜索,然后根据坐标按地理空间索引排序。
因此,如果我搜索“The Most”这个词,它将按名称搜索“The Most”并返回最近的酒店,其中包含“The Most in them。”
mongodb甚至支持这种类型的搜索吗?
我正在阅读mongodb的指导:https://docs.mongodb.org/manual/core/index-text/
复合文本索引不能包含任何其他特殊索引类型, 例如多键或地理空间索引字段。
据我了解,我不是在创建复合文本索引。这是一个简单的文本索引,这意味着我只是为name
字段的文本编制索引,而不是为city
和name
字段编制索引。
答案 0 :(得分:25)
有一个公平的案例,你真的根本不需要这个,因为很难证明这种操作的用例是合理的,我认为"正在搜索酒店&# 34; 不是" text"的组合。和#34;地理空间"搜索确实适用。
实际上"大多数人" 会寻找靠近某个位置的内容,甚至更可能靠近他们想要访问的各个位置 ,作为其主要标准的一部分,然后是其他"获胜者"可能会更加重视"成本","评级","品牌","设施",甚至可能接近餐馆等。
将"文本搜索" 添加到该列表是非常不同的东西,在这个特定的应用程序中可能没有太多实际用途。
尽管如此,这可能值得一些解释,这里有一些概念可以理解这两个概念为什么不真正"网格" 用于此用途至少是这样的。
首先,我想建议"调整"你的数据模式有点:
{
"name" : "The Most Amazing Hotel",
"city" : "India",
"location": {
"type": "Point",
"coordinates": [
72.867804,
19.076033
]
}
}
至少证明"location"
是一个有效的GeoJSON对象用于索引,你通常需要GeoJSON而不是传统的坐标对,因为它确实为查询和存储打开了更多的选项,加上距离是标准化为米而不是等同的"弧度"全球各地。
因此,您的阅读基本上是正确的,因为您不能同时使用多个特殊索引。首先看一下复合索引定义:
db.hotels.createIndex({ "name": "text", "location": "2dsphere" })
{ " OK" :0, " ERRMSG" :"错误的索引键模式{name:\" text \&#34 ;, location:\" 2dsphere \" }:不能为单个索引使用多个索引插件。", "代码" :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和geoNear不允许在同一个查询"中, "代码" :2 }:undefined
这实际上支持了以下三种方式无法在复合索引中定义的原因:
正如初始错误所示,这些"特殊的方式"在MongoDB中处理索引基本上需要"分支"到#34;特别"所选索引类型的处理程序,并且这两个处理程序不在同一个地方。
即使使用单独的索引,由于逻辑基本上是"和"条件,MongoDB无论如何都不能实际选择多个索引,因为两个查询子句都需要" special"事实上,处理它实际上是必须的。它不能。
即使这在逻辑上是$or
条件,你基本上也会回到第1点,即使应用"索引交叉点"还有另外一种特殊的"特殊的"他们必须的索引应用于"顶级"查询操作以允许索引选择。将它们包含在$or
中意味着MongoDB无法做到这一点,因此不允许这样做。
所以每个基本上都必须是独家的,你不能一起使用它们。但当然,你可以随时“欺骗”,这取决于哪种搜索顺序对你来说更重要。
通过" location"第一:
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()
仔细查看每种方法中的选择选项以查看发生的情况,但基本情况是每种方法只选择一个要分别使用的特殊索引。
在第一种情况下,它将是用于主要集合的集合上的geoSpatial索引,并将根据它们与首先给定的位置的接近度找到结果,然后通过为{{1}给出的正则表达式参数进行过滤} field。
在第二种情况下,它将使用"文本"用于进行主要选择的索引(因此找到东西"惊人的"首先)并从这些结果中应用带有$geoWithin
的地理空间过滤器(不使用索引),在这种情况下执行的基本上是通过在提供的距离内的circle around a point内搜索来过滤结果,相当于name
正在做的事情。
但要考虑的关键是每种方法都有可能返回不同的结果。通过首先缩小位置,唯一可以检查的数据是指定距离内的那些位置,所以任何东西都是"惊人的"额外的过滤器永远不会考虑距离之外的距离。
在第二种情况下,由于文本字词是主要搜索字词,因此所有结果为" Amazing"被考虑在内,辅助过滤器可以返回的仅项是允许从初始文本过滤器返回的项。
这在整体考虑中非常重要,因为两个查询操作(" text"" geoSpatial")努力实现非常不同的事情。在"文本"它正在寻找"最佳结果"到给定的术语,并且本质上仅返回与排名顺序中的术语匹配的限制数量的结果。这意味着在应用任何其他过滤条件时,很可能满足第一个条件的许多项目不符合附加条件。
简而言之,'并非所有事情都是"惊人的"必须接近查询点,这意味着具有像$near
这样的现实限制,并且通过最佳匹配,这些100可能不包含所有" near"项目也是如此。
此外,100 results
运算符实际上并不真正排序"结果本身任何方式。它的主要目的实际上不仅仅是“匹配”。在一个短语上,但"score"结果,以便漂浮"最佳"匹配到顶部。这通常是在"之后完成的。查询本身的投影值是"排序"并且很可能"有限"正如刚才提到的。可能在聚合管道中执行此操作然后应用第二个过滤器,但如上所述,这可能排除了否则"接近"在另一个目的。
相反的情况也可能是正确的('有很多"惊人的"事情远离点#' ),但是由于实际的距离限制,这变成了不太可能。但另一个考虑因素是这不是 true 文本搜索,而只是使用正则表达式来匹配给定的术语。
作为最后一点,我总是在此处使用$text
作为示例词组,而不是问题中建议的"Amazing"
。这是因为"阻止"这里的文本索引(以及大多数专用文本搜索产品)都适用于特定术语忽略,非常类似于"和#34;,"或","",偶数" in" 也是如此,因为它们并不真正被认为是有价值到一个短语,这是文本搜索的作用。
因此,事实上,正则表达式实际上更适合匹配这些术语,如果确实需要的话。
这真的让我们回到原点,在那个"文本"查询真的不属于这里。其他有用的过滤器通常与真正的" geoSpatial"搜索条件越好,真正的文本搜索"在重要的事项清单上真的很低。
更有可能的是,人们想要一个位于*" Set Intersection"距他们希望访问的目的地的距离,或者至少接近一些或大多数的距离。当然,如前所述的其他因素(*"价格","服务"等)是人们普遍需要的东西。
通过这种方式寻找结果并不是"良好的契合" 。如果你认为你真的必须,那么应用其中一个"作弊"方法,或实际上使用不同的查询,然后使用其他一些逻辑来合并每组结果。但是服务器单独执行此操作确实没有意义,这就是它不尝试的原因。
所以我会专注于首先让你的geoSpatial匹配正确,然后应用其他对结果很重要的评论。但我真的不相信"文本搜索"无论如何,真的有效。 "作弊"相反,但只有你真的必须。