Cómo buscar una parte de una palabra con ElasticSearch


128

Recientemente comencé a usar ElasticSearch y parece que no puedo hacer que busque una parte de una palabra.

Ejemplo: tengo tres documentos de mi couchdb indexados en ElasticSearch:

{
  "_id" : "1",
  "name" : "John Doeman",
  "function" : "Janitor"
}
{
  "_id" : "2",
  "name" : "Jane Doewoman",
  "function" : "Teacher"
}
{
  "_id" : "3",
  "name" : "Jimmy Jackal",
  "function" : "Student"
} 

Así que ahora, quiero buscar todos los documentos que contengan "Doe"

curl http://localhost:9200/my_idx/my_type/_search?q=Doe

Eso no devuelve ningún golpe. Pero si busco

curl http://localhost:9200/my_idx/my_type/_search?q=Doeman

Sí devuelve un documento (John Doeman).

Intenté configurar diferentes analizadores y diferentes filtros como propiedades de mi índice. También he intentado usar una consulta completa (por ejemplo:

{
  "query": {
    "term": {
      "name": "Doe"
    }
  }
}

) Pero nada parece funcionar.

¿Cómo puedo hacer que ElasticSearch encuentre a John Doeman y Jane Doewoman cuando busco "Doe"?

ACTUALIZAR

Traté de usar el tokenizer y filtro nGram, como propuso Igor, así:

{
  "index": {
    "index": "my_idx",
    "type": "my_type",
    "bulk_size": "100",
    "bulk_timeout": "10ms",
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "my_ngram_tokenizer",
          "filter": [
            "my_ngram_filter"
          ]
        }
      },
      "filter": {
        "my_ngram_filter": {
          "type": "nGram",
          "min_gram": 1,
          "max_gram": 1
        }
      },
      "tokenizer": {
        "my_ngram_tokenizer": {
          "type": "nGram",
          "min_gram": 1,
          "max_gram": 1
        }
      }
    }
  }
}

El problema que tengo ahora es que todas y cada una de las consultas devuelven TODOS los documentos. Cualquier puntero? La documentación de ElasticSearch sobre el uso de nGram no es excelente ...


9
no es de extrañar, tienes un ngram mínimo / máximo establecido en 1, así que 1 letra :)
Martin B.

Respuestas:


85

También estoy usando nGram. Uso tokenizer estándar y nGram solo como filtro. He aquí mi arreglo:

{
  "index": {
    "index": "my_idx",
    "type": "my_type",
    "analysis": {
      "index_analyzer": {
        "my_index_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "mynGram"
          ]
        }
      },
      "search_analyzer": {
        "my_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "standard",
            "lowercase",
            "mynGram"
          ]
        }
      },
      "filter": {
        "mynGram": {
          "type": "nGram",
          "min_gram": 2,
          "max_gram": 50
        }
      }
    }
  }
}

Vamos a encontrar partes de palabras de hasta 50 letras. Ajuste el max_gram como lo necesite. En palabras alemanas puede ser realmente grande, así que lo configuré en un valor alto.



¿Es eso lo que obtienes de la configuración del índice o es lo que publicas en elasticsearch para configurarlo?
Tomas Jansson

Es una POST para configurar Elasticsearch.
roka

No estoy firme con las versiones actuales de Elasticsearch, pero debería mencionar que en la documentación: elastic.co/guide/en/elasticsearch/reference/current/index.html
roka

1
@ JimC No he usado ElasticSearch durante al menos 7 años, por lo que no sé los cambios actuales del proyecto.
roka

63

La búsqueda con comodines iniciales y finales será extremadamente lenta en un índice grande. Si desea poder buscar por prefijo de palabra, elimine el comodín inicial. Si realmente necesita encontrar una subcadena en medio de una palabra, sería mejor usar el tokenizer ngram.


14
Igor tiene razón. Al menos, elimine los principales *. Para el ejemplo de NGram ElasticSearch, vea esta esencia: gist.github.com/988923
karmi

3
@karmi: ¡Gracias por tu completo ejemplo! Quizás desee agregar su comentario como una respuesta real, es lo que lo hizo funcionar para mí y lo que me gustaría votar.
Fabian Steeg

54

Creo que no hay necesidad de cambiar ningún mapeo. Intenta usar query_string , es perfecto. Todos los escenarios funcionarán con el analizador estándar predeterminado:

Tenemos datos:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

Escenario 1:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*Doe*"}
} }

Respuesta:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

Escenario 2:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*Jan*"}
} }

Respuesta:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}

Escenario 3:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*oh* *oe*"}
} }

Respuesta:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

EDITAR - Misma implementación con búsqueda elástica de datos de resorte https://stackoverflow.com/a/43579948/2357869

Una explicación más de cómo query_string es mejor que otros https://stackoverflow.com/a/43321606/2357869


3
Creo que esto es lo más fácil
Esgi Dendyanri

Si . Lo he implementado en mi proyecto.
Opster Elasticsearch Pro-Vijay

¿Cómo incluir múltiples campos para buscar?
Shubham A.

intente esto: - {"query": {"query_string": {"fields": ["content", "name"], "query": "this AND that"}}}
Opster Elasticsearch Pro-Vijay


14

sin cambiar sus asignaciones de índice, puede hacer una simple consulta de prefijo que realizará búsquedas parciales como espera

es decir.

{
  "query": { 
    "prefix" : { "name" : "Doe" }
  }
}

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-prefix-query.html


¿puedes hacer una búsqueda de campo múltiple usando la consulta de prefijo?
batmaci

Gracias, justo lo que estaba buscando! ¿Alguna idea sobre el impacto en el rendimiento?
Vingtoft

6

Pruebe la solución que se describe aquí: Búsquedas de subcadenas exactas en ElasticSearch

{
    "mappings": {
        "my_type": {
            "index_analyzer":"index_ngram",
            "search_analyzer":"search_ngram"
        }
    },
    "settings": {
        "analysis": {
            "filter": {
                "ngram_filter": {
                    "type": "ngram",
                    "min_gram": 3,
                    "max_gram": 8
                }
            },
            "analyzer": {
                "index_ngram": {
                    "type": "custom",
                    "tokenizer": "keyword",
                    "filter": [ "ngram_filter", "lowercase" ]
                },
                "search_ngram": {
                    "type": "custom",
                    "tokenizer": "keyword",
                    "filter": "lowercase"
                }
            }
        }
    }
}

Para resolver el problema del uso del disco y el problema del término de búsqueda demasiado largo, se utilizan ngrams cortos de 8 caracteres de longitud (configurados con: "max_gram": 8 ). Para buscar términos con más de 8 caracteres, convierta su búsqueda en una consulta AND booleana que busque cada subcadena distinta de 8 caracteres en esa cadena. Por ejemplo, si un usuario buscó un patio grande (una cadena de 10 caracteres), la búsqueda sería:

"arge ya Y arge yar Y rge yard .


2
enlace muerto, por favor arregle
DarkMukke

He estado buscando algo como esto por un tiempo. ¡Gracias! ¿Sabes cómo se escala la memoria con min_gramy max_gramparece que dependería linealmente del tamaño de los valores de campo y el rango de miny max? ¿Qué tan mal visto está usando algo como esto?
Glen Thompson

¿También hay alguna razón para que ngramhaya un filtro sobre un tokenizador? ¿no podría simplemente tenerlo como un tokenizador y luego aplicar un filtro en minúsculas? index_ngram: { type: "custom", tokenizer: "ngram_tokenizer", filter: [ "lowercase" ] }Lo probé y parece dar los mismos resultados usando la API de prueba del analizador
Glen Thompson

2

Si desea implementar la funcionalidad de autocompletar, entonces Completion Suggester es la solución más clara. La siguiente publicación de blog contiene una descripción muy clara de cómo funciona esto.

En dos palabras, es una estructura de datos en memoria llamada FST que contiene sugerencias válidas y está optimizada para una recuperación rápida y uso de memoria. Esencialmente, es solo un gráfico. Por ejemplo, y FST que contiene las palabras hotel, marriot, mercure, muncheny munichse vería así:

ingrese la descripción de la imagen aquí


2

puedes usar regexp.

{ "_id" : "1", "name" : "John Doeman" , "function" : "Janitor"}
{ "_id" : "2", "name" : "Jane Doewoman","function" : "Teacher"  }
{ "_id" : "3", "name" : "Jimmy Jackal" ,"function" : "Student"  } 

si usa esta consulta:

{
  "query": {
    "regexp": {
      "name": "J.*"
    }
  }
}

le dará todos los datos que comienzan con "J". Considere que desea recibir solo los dos primeros registros que terminan con "man" para que pueda usar esta consulta:

{
  "query": { 
    "regexp": {
      "name": ".*man"
    }
  }
}

y si desea recibir todos los registros que en su nombre existen "m", puede usar esta consulta:

{
  "query": { 
    "regexp": {
      "name": ".*m.*"
    }
  }
}

Esto funciona para mí. Y espero que mi respuesta sea adecuada para resolver su problema.


1

El uso de wilcards (*) previene el cálculo de una puntuación


1
¿Podría agregar más detalles a su respuesta? Proporcione un código de muestra o una referencia a la documentación sobre lo que hace.
Cray

0

Estoy usando esto y conseguí trabajar

"query": {
        "query_string" : {
            "query" : "*test*",
            "fields" : ["field1","field2"],
            "analyze_wildcard" : true,
            "allow_leading_wildcard": true
        }
    }

-6

No importa.

Tuve que mirar la documentación de Lucene. ¡Parece que puedo usar comodines! :-)

curl http://localhost:9200/my_idx/my_type/_search?q=*Doe*

¡Hace el truco!


11
Ver @imotov respuesta. El uso de comodines no va a escalar bien en absoluto.
Mike Munroe

55
@Idx: vea cómo se rechaza su propia respuesta. Los votos negativos representan la calidad y la relevancia de una respuesta. ¿Podrías dedicar un minuto a aceptar la respuesta correcta? Al menos los nuevos usuarios te lo agradecerán.
asyncwait

3
Suficientes votos negativos. OP dejó en claro cuál es la mejor respuesta ahora. +1 por compartir la que parecía ser la mejor respuesta antes de que alguien publicara una mejor.
s.Daniel
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.