web-dev-qa-db-fra.com

Comment rechercher une partie d'un mot avec ElasticSearch

J'ai récemment commencé à utiliser ElasticSearch et je n'arrive pas à le faire rechercher une partie d'un mot.

Exemple: J'ai trois documents de ma couche 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"
} 

Alors 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

Il ne renvoie qu'un document (John Doeman).

J'ai essayé de définir différents analyseurs et différents filtres en tant que 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 en sorte qu'ElasticSearch trouve John Doeman et Jane Doewoman lorsque je recherche "Doe"?

METTRE À JOUR

J'ai essayé d'utiliser le tokenizer et le filtre nGram, comme proposé par Igor, 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 maintenant est que chaque requête renvoie TOUS les documents . Des pointeurs? La documentation ElasticSearch sur l'utilisation de nGram n'est pas géniale ...

94
ldx

J'utilise aussi nGram. J'utilise tokenizer standard et nGram comme 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
        }
      }
    }
  }
}

Vous permet de trouver des pièces Word jusqu'à 50 lettres. Ajustez le max_gram selon vos besoins. En allemand, les mots peuvent devenir très gros, alors je le mets au plus haut.

73
roka

La recherche avec des caractères génériques de début et de fin sera extrêmement lente pour un index volumineux. Si vous souhaitez pouvoir effectuer une recherche par préfixe Word, supprimez le caractère générique de début. Si vous avez vraiment besoin de trouver une sous-chaîne au milieu d'un mot, mieux vaut utiliser ngram tokenizer. 

57
imotov

Je pense qu'il n'est pas nécessaire de changer de mapping . Essayez d'utiliser query_string, 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 recherche élastique des données de ressort https://stackoverflow.com/a/43579948/2357869

Une explication supplémentaire sur la façon dont query_string est meilleur que les autres https://stackoverflow.com/a/43321606/2357869

25
Vijay Gupta

sans changer vos correspondances d'index, vous pouvez faire une simple requête préfixe qui fera des recherches partielles comme vous l'espérez

c'est à dire.

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

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

11

Essayez la solution avec est 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 d'utilisation du disque et le problème de la durée de recherche trop longue, il est utilisé 8 caractères ngrams (configuration: "max_gram": 8). Pour rechercher des termes de plus de 8 caractères, transformez votre recherche en une requête booléenne ET recherchant chaque sous-chaîne de 8 caractères distincte dans cette chaîne. Par exemple, si un utilisateur a recherché large yard (une chaîne de 10 caractères), la recherche serait:

"arge ya ET arge yar ET rge yard.

Si vous souhaitez implémenter la fonctionnalité de saisie semi-automatique, alors Completion Suggester est la solution la plus soignée. Le prochain article blog contient une description très claire de son fonctionnement.

En deux mots, il s’agit d’une structure de données en mémoire appelée FST, qui contient des suggestions valides et qui est optimisée pour une récupération rapide et une utilisation de la mémoire. Essentiellement, ce n’est qu’un graphique. Par exemple, et FST contenant les mots hotel, marriot, mercure, munchen et munich ressemblerait à ceci:

enter image description here

1
Neshta

vous pouvez utiliser regexp.

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

si vous utilisez cette requête:

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

vous donnerez à toutes les données dont le nom commence par "J" .Considérez que vous souhaitez recevoir uniquement les deux premiers enregistrements dont le nom se termine par "man" afin que vous puissiez utiliser cette requête:

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

et si vous voulez recevoir tous les enregistrements qui, dans leur nom, existent "m", vous pouvez utiliser cette requête:

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

Cela fonctionne pour moi. Et j'espère que ma réponse sera appropriée pour résoudre votre problème.

0
Ali Moshiri

L'utilisation de caractères génériques (*) empêche le calcul d'un score

0
Dardino