Pregunta Uso de búsqueda de texto completo con índice geoespacial en Mongodb


Digamos que quiero desarrollar una aplicación de Android que permita a un usuario buscar un hotel que esté más cerca de donde se encuentra. Esto es muy común en las aplicaciones de hoy en día, como AirBnb, por ejemplo.

Este es el conjunto de datos que estoy usando:

{
    "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
      ]
}

Ahora, quiero crear un índice de texto sobre el name campo para que busque por nombre y luego ordene por un índice geoespacial basado en las coordenadas.

Así que si busco las palabras "The Most", buscaré por el nombre las palabras "The Most" y devolveré los hoteles más cercanos con las palabras "The Most en ellas".

¿Admite mongodb este tipo de búsqueda?

Estoy leyendo la guía para mongodb aquí: https://docs.mongodb.org/manual/core/index-text/

Un índice de texto compuesto no puede incluir ningún otro tipo de índice especial,   como campos de índice geoespacial o multi-clave.

Por lo que entiendo, no estoy creando un índice de texto compuesto. Este es un índice de texto simple, lo que significa que solo estoy indexando el texto para el name campo y no para el city Y name campos.


5
2017-11-17 19:20


origen


Respuestas:


Hay un caso justo de que realmente no necesita esto en absoluto, ya que es muy difícil justificar un caso de uso para tal operación, y yo diría que "Buscando un hotel" no es algo en lo que realmente se aplique una combinación de "texto" y búsqueda "geoespacial".

En realidad "la mayoría de la gente" estaría buscando algo cerca de una ubicación, o incluso más probable cerca de varios lugares que quieren visitar, como parte de sus criterios principales, y luego otros "ganadores" probablemente serían más ponderados para "costo", "calificación", "marca", "instalaciones", y probablemente incluso proximidad a restaurantes, etc..

Añadiendo "Búsqueda de texto" a esa lista hay un cosa muy diferente y probablemente no de mucho uso real en esta aplicación en particular.

Aún así, esto probablemente merece alguna explicación, y hay algunos conceptos para entender aquí en cuanto a por qué los dos conceptos no realmente "malla" para este caso de uso al menos.

Esquema de fijación

En primer lugar, me gustaría hacer una sugerencia para "modificar" un poco su esquema de datos:

{
    "name" : "The Most Amazing Hotel",
    "city" : "India",
    "location": {
        "type": "Point",
        "coordinates": [
               72.867804,
               19.076033
        ]
    }
}

Eso al menos lo prueba. "location" como un objeto GeoJSON válido para la indexación, y generalmente desea GeoJSON en lugar de pares de coordenadas heredados, ya que abre más opciones para consultas y almacenamiento en general, más las distancias se estandarizan a metros en lugar de los "radianes" equiparables alrededor del globo.

¿Por qué no trabajan juntos?

Por lo tanto, su lectura es básicamente correcta, ya que no puede utilizar más de un índice especial a la vez. Primero, mira la definición del índice compuesto:

db.hotels.createIndex({ "name": "text", "location": "2dsphere" })

{           "ok": 0,           "errmsg": "patrón de clave de índice incorrecto {nombre: \" texto \ ", ubicación: \" 2dsphere \ "}: No se puede usar más de un complemento de índice para un solo índice.",           "código": 67}

Así que eso no se puede hacer. Incluso considerando por separado:

db.hotels.createIndex({ "name": "text" })
db.hotels.createIndex({ "location": "2dsphere" })

A continuación, intente hacer una consulta:

db.hotels.find({
    "location": {
        "$nearSphere": {
            "$geometry": {
                "type": "Point",
                "coordinates": [
                   72.867804,
                   19.076033
                ]
            }
        }
    },
    "$text": { "$search": "Amazing" }
})

Error: el comando falló: {           "waitedMS": NumberLong (0),           "ok": 0,           "errmsg": "texto y geoNear no permitidos en la misma consulta",           "código": 2   }: indefinido

Lo que en realidad respalda las razones por las que esto no se pudo definir en un índice compuesto de tres maneras:

  1. Como indica el error inicial, la forma en que estos índices "especiales" se manejan en MongoDB requiere esencialmente "derivar" al controlador "especial" para el tipo de índice seleccionado, y los dos controladores no viven en el mismo lugar.

  2. Incluso con índices separados, dado que la lógica es básicamente una condición "y", MongoDB no puede seleccionar más de un índice de todos modos, y dado que ambas cláusulas de consulta requieren un manejo "especial", de hecho sería necesario hacerlo. Y no puede.

  3. Incluso si esto fuera lógicamente un $or condición, básicamente termina en el punto 1, donde incluso aplicando "intersección de índice" hay otra propiedad de tales índices "especiales" que debe se aplicará en el "nivel superior" de las operaciones de consulta para permitir la selección del índice. Envolviendo estos en una $or significa que MongoDB no puede hacer eso y por lo tanto no está permitido.

Pero puedes "engañar"

Así que, básicamente, cada uno tiene que ser exclusivo y no se pueden usar juntos. Pero, por supuesto, siempre puede "hacer trampa", dependiendo del orden de búsqueda que sea más importante para usted.

Ya sea por "ubicación" primero:

db.hotels.aggregate([
    { "$geoNear": {
        "near": {
            "type": "Point",
            "coordinates": [
               72.867804,
               19.076033
            ]
        },
        "spherical": true,
        "maxDistance": 5000,
        "distanceField": "distance",
        "query": {
           "name": /Amazing/
        }
    }}
])

O incluso:

db.hotels.find({
    "location": {
        "$nearSphere": {
            "$geometry": {
                "type": "Point",
                "coordinates": [
                   72.867804,
                   19.076033
                ]
            },
            "$maxDistance": 5000
        }
    },
    "name": /Amazing/
})

O por búsqueda de texto primero:

db.hotels.find({
    "$text": { "$search": "Amazing" },
    "location": {
        "$geoWithin": {
            "$centerSphere": [[
               72.867804,
               19.076033
            ], 5000 ]
        }
    }
})

Ahora puede ver de cerca las opciones de selección en cada enfoque con .explain() para ver qué está sucediendo, pero el caso básico es que cada uno selecciona solo uno de los índices especiales para usar respectivamente.

En el primer caso, será el índice geoespacial de la colección que se utiliza para el primario y encontrará los resultados en función de su proximidad a la ubicación dada primero y luego se filtrará por el argumento de Expresión regular proporcionado para el name campo.

En el segundo caso, usará el índice de "texto" para hacer la selección primaria (por lo tanto, primero encontrará las cosas "Increíbles") y a partir de esos resultados aplique un filtro geoespacial (sin usar un índice) con $geoWithin, que en este caso está realizando lo que es básicamente el equivalente de lo que $near está haciendo, buscando dentro de un circula alrededor de un punto dentro de la distancia suministrada para filtrar los resultados allí.

No todas las consultas son iguales

Sin embargo, la cuestión clave a considerar es que es muy posible que cada enfoque arroje resultados diferentes. Al reducir primero la ubicación, los únicos datos que se pueden inspeccionar son las ubicaciones dentro de la distancia especificada, por lo que el filtro adicional nunca considerará nada que sea "Increíble" fuera de la distancia.

En el segundo caso, dado que el término de texto es la búsqueda principal, entonces todas los resultados de "Amazing" se ponen en consideración, y el solamente los elementos que pueden ser devueltos por el filtro secundario son aquellos que se permitieron devolver desde el filtro de texto inicial.

Esto es muy importante en la consideración general ya que las dos operaciones de consulta (tanto "texto" como "geoespacial") se esfuerzan por lograr cosas muy diferentes. En el caso de "texto", busca los "mejores resultados" para el término dado, y por naturaleza solo devolverá un número limitado de resultados que coincidan con el término en orden de clasificación. Esto significa que cuando se aplica cualquier otra condición de filtro, existe una gran posibilidad de que muchos de los artículos que cumplieron con esa primera condición no cumplan con los criterios adicionales.

En breve, 'No todas las cosas "asombrosas" están necesariamente en algún lugar cerca del punto consultado', lo que significa con un límite realista como 100 resultsy, según la mejor coincidencia, es probable que esos 100 no contengan todos los elementos "cercanos".

También el $text el operador realmente no "ordena" los resultados de ninguna manera por sí mismo. Su propósito principal es, de hecho, no solo "coincidir" en una frase sino también "Puntuación" El resultado con el fin de flotar el "mejor" partido a la parte superior. Esto se hace típicamente "después" de la consulta en sí, con el valor proyectado siendo "ordenado" y muy probablemente "limitado" como se mencionó anteriormente. Posible en las tuberías de agregación para hacer eso y luego aplicar el segundo filtro (s), pero como se indicó, es probable que excluya cosas que de otro modo estarían "cerca" en el otro propósito.

Lo contrario también es probable que sea cierto ( "Hay muchas cosas" asombrosas "más alejadas del punto" ), pero con límites de distancia realistas esto se vuelve menos probable. Pero la otra consideración dada es que esto no es cierto búsqueda de texto, pero solo usando una expresión regular para coincidir con el término dado.

Como nota final, siempre estoy usando "Amazing" como la frase de ejemplo aquí y no "Most" como se sugiere en la pregunta. Esto se debe a cómo funciona la "derivación" en los índices de texto aquí (así como en la mayoría de los productos de búsqueda de texto dedicados) en que el término particular sería ignorado, Muy parecido "y", "o", "el", incluso "en" sería también, ya que en realidad no se consideran valioso a una Frase, que es lo que hace la búsqueda de texto.

Entonces, de hecho, sigue siendo que una expresión regular, en realidad sería mejor para hacer coincidir dichos términos, si es que eso era realmente necesario.

Concluyendo

Lo que realmente nos lleva de vuelta al círculo completo hasta el punto original, en el sentido de que una consulta de "texto" realmente no pertenece aquí de todos modos. Los otros filtros útiles usualmente funcionan en conjunto con los verdaderos criterios de búsqueda "geoespaciales", mejor, y la verdadera "búsqueda de texto" es realmente baja en la lista de lo que sería importante.

Lo más probable es que la gente quiera una ubicación que se encuentre dentro de un * "Establecer intersección" de las distancias de los destinos deseados que desea visitar, o al menos lo suficientemente cerca de algunos, o la mayoría. Entonces, por supuesto, otros factores (* "precio", "servicio", etc.) como se mencionó anteriormente son cosas que la gente quiere en general.

No es realmente un "buen ajuste" para buscar los resultados de esta manera. Si crees que realmente debes hacerlo, entonces aplica uno de los enfoques "engañosos" o, de hecho, utiliza diferentes consultas y luego otra lógica para fusionar cada conjunto de resultados. Pero realmente no tiene sentido que el servidor haga esto solo, y es por eso que no lo intenta.

Por lo tanto, me concentraría primero en hacer coincidir tus coincidencias geoespaciales y luego aplicar otros criterios que deberían ser importantes para los resultados. Pero realmente no creo que la "búsqueda de texto" sea realmente válida para ser uno de ellos de todos modos. "Truco" en cambio, pero solo si realmente debes hacerlo.


16
2017-11-18 06:41