158 votes

Comment rechercher une partie d'un mot avec ElasticSearch

J'ai récemment commencé à utiliser ElasticSearch et je n'arrive pas à faire en sorte qu'il recherche une partie d'un mot.

Exemple : J'ai trois documents de mon couchdb indexés dans ElasticSearch :

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

Donc maintenant, je veux rechercher tous les documents contenant "Doe".

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

Cela ne renvoie aucun résultat. Mais si je cherche

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

Elle renvoie un document (John Doeman).

J'ai essayé de définir différents analyseurs et différents filtres comme propriétés de mon index. J'ai également essayé d'utiliser une requête complète (par exemple :

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

) Mais rien ne semble fonctionner.

Comment faire pour qu'ElasticSearch trouve à la fois John Doeman et Jane Doewoman lorsque je recherche "Doe" ?

UPDATE

J'ai essayé d'utiliser le tokenizer et le filtre nGram, comme Igor l'a proposé, comme ceci :

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

Le problème que je rencontre actuellement est que chaque requête renvoie TOUS les documents. Des pistes ? La documentation d'ElasticSearch sur l'utilisation de nGram n'est pas très bonne...

10 votes

Pas étonnant, vous habe min/max ngram réglé sur 1, donc 1 lettre :)

3 votes

Je suis en fait surpris que l'ES ne rende pas cela plus facile. C'est ElasticSearch, pas ElasticExactMatchUnlessIDoSomeCeremony.

92voto

roka Points 464

J'utilise aussi nGram. J'utilise le tokenizer standard et nGram juste comme un filtre. Voici ma configuration :

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

Il vous permet de trouver des parties de mots jusqu'à 50 lettres. Ajustez le max_gram selon vos besoins. En allemand, les mots peuvent être très grands, donc je l'ai fixé à une valeur élevée.

0 votes

C'est ce que vous obtenez des paramètres de l'index ou c'est ce que vous envoyez à elasticsearch pour le configurer ?

0 votes

C'est un POST pour configurer Elasticsearch.

73voto

Vijay Gupta Points 1669

Je pense qu'il n'y a pas besoin de changer de cartographie. Essayez d'utiliser requête_chaîne c'est parfait. Tous les scénarios fonctionneront avec l'analyseur standard par défaut :

Nous avons des données :

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

Scénario 1 :

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

Réponse :

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

Scénario 2 :

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

Réponse :

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

Scénario 3 :

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

Réponse :

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

EDIT - Même implémentation avec spring data elastic search https://stackoverflow.com/a/43579948/2357869

Une explication supplémentaire de la supériorité de query_string sur les autres. https://stackoverflow.com/a/43321606/2357869

70voto

imotov Points 15513

La recherche avec des jokers de tête et de queue sera extrêmement lente sur un grand index. Si vous voulez être en mesure de rechercher par préfixe de mot, supprimez le joker de tête. Si vous avez vraiment besoin de trouver une sous-chaîne au milieu d'un mot, vous feriez mieux d'utiliser ngram tokenizer.

15 votes

Igor a raison. Il faut au moins enlever le * de tête. Pour l'exemple de NGram ElasticSearch, voir ce gist : gist.github.com/988923

3 votes

@karmi : Merci pour votre exemple complet ! Peut-être que vous voulez ajouter votre commentaire comme une réponse réelle, c'est ce qui a fonctionné pour moi et ce que je voudrais upvote.

16voto

Sans modifier vos mappages d'index, vous pouvez faire une simple requête préfixe qui effectuera des recherches partielles comme vous le souhaitez.

ie.

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

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

6voto

Essayez la solution décrite ici : Recherches de sous-chaînes exactes dans 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"
                }
            }
        }
    }
}

Pour résoudre le problème de l'utilisation du disque et celui des termes de recherche trop longs : 8 caractères courts ngrammes sont utilisés (configurés avec : "max_gram" : 8 ). Pour rechercher des termes comportant plus de 8 caractères, transformez votre recherche en une requête booléenne ET recherchant chaque sous-chaîne distincte de 8 caractères dans cette chaîne. Par exemple, si un utilisateur recherche grande cour (une chaîne de 10 caractères), la recherche serait :

"arge ya AND arge yar AND rge yard". .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X