在Mongodb上使用地理空间索引的全文搜索

时间:2015-11-17 19:20:57

标签: mongodb full-text-search mongodb-query geospatial

假设我想开发一个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字段的文本编制索引,而不是为cityname字段编制索引。

1 个答案:

答案 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

这实际上支持了以下三种方式无法在复合索引中定义的原因:

  1. 正如初始错误所示,这些"特殊的方式"在MongoDB中处理索引基本上需要"分支"到#34;特别"所选索引类型的处理程序,并且这两个处理程序不在同一个地方。

  2. 即使使用单独的索引,由于逻辑基本上是"和"条件,MongoDB无论如何都不能实际选择多个索引,因为两个查询子句都需要" special"事实上,处理它实际上是必须的。它不能。

  3. 即使这在逻辑上是$or条件,你基本上也会回到第1点,即使应用"索引交叉点"还有另外一种特殊的"特殊的"他们必须的索引应用于"顶级"查询操作以允许索引选择。将它们包含在$or中意味着MongoDB无法做到这一点,因此不允许这样做。

  4. 但你可以"作弊"

    所以每个基本上都必须是独家的,你不能一起使用它们。但当然,你可以随时“欺骗”,这取决于哪种搜索顺序对你来说更重要。

    通过" 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匹配正确,然后应用其他对结果很重要的评论。但我真的不相信"文本搜索"无论如何,真的有效。 "作弊"相反,但只有你真的必须。