Comment rechercher une partie d'un mot avec ElasticSearch


128

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

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 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 puis-je faire en sorte qu'ElasticSearch trouve à la fois John Doeman et Jane Doewoman lorsque je recherche "Doe"?

METTRE À JOUR

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 j'ai 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 ...


9
pas étonnant, vous avez un ngram min / max réglé à 1, donc 1 lettre :)
Martin B.

Réponses:


85

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

Laissez-vous trouver des parties de mot jusqu'à 50 lettres. Ajustez le max_gram selon vos besoins. En allemand, les mots peuvent devenir vraiment gros, alors je l'ai défini sur une valeur élevée.



Est-ce ce que vous obtenez des paramètres de l'index ou est-ce ce que vous publiez sur elasticsearch pour le configurer?
Tomas Jansson

C'est un POST pour configurer Elasticsearch.
roka

Je ne suis pas ferme avec les versions actuelles d'Elasticsearch, mais je devrais le mentionner dans la documentation: élastique.co
guide/en

1
@JimC Je n'ai pas utilisé ElasticSearch depuis au moins 7 ans, donc je ne connais pas les changements actuels du projet.
roka

63

La recherche avec des caractères génériques de début et de fin sera extrêmement lente sur un index volumineux. Si vous souhaitez pouvoir effectuer une recherche par préfixe de mot, 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, vous feriez mieux d'utiliser le tokenizer ngram.


14
Igor a raison. Supprimez au moins le début *. Pour l'exemple NGram ElasticSearch, voir l'essentiel: gist.github.com/988923
karmi

3
@karmi: Merci pour votre exemple complet! Peut-être voulez-vous ajouter votre commentaire en tant que réponse réelle, c'est ce qui a fait que cela a fonctionné pour moi et ce que je voudrais voter.
Fabian Steeg

54

Je pense qu'il n'est pas nécessaire de changer de mappage. 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 la recherche élastique de données de ressort https://stackoverflow.com/a/43579948/2357869

Une autre explication comment query_string est meilleur que les autres https://stackoverflow.com/a/43321606/2357869


3
Je pense que c'est le plus simple
Esgi Dendyanri

Oui . J'ai implémenté dans mon projet.
Opster Elasticsearch Pro-Vijay

Comment inclure plusieurs champs dans lesquels rechercher?
Shubham A.

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


14

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

c'est à dire.

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

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


pouvez-vous effectuer une recherche multi-champs en utilisant une requête de préfixe?
batmaci

Merci, exactement ce que je cherchais! Des réflexions sur l'impact sur les performances?
Vingtoft

6

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 du terme de recherche trop long, des ngrammes courts de 8 caractères sont utilisés (configurés avec: "max_gram": 8 ). Pour rechercher des termes de plus de 8 caractères, transformez votre recherche en une requête booléenne AND recherchant chaque sous-chaîne distincte de 8 caractères dans cette chaîne. Par exemple, si un utilisateur recherchait une grande cour (une chaîne de 10 caractères), la recherche serait:

"arge ya ET arge yar ET rge yard .


2
lien mort, pls fix
DarkMukke

Je cherchais quelque chose comme ça depuis un moment. Je vous remercie! Savez-vous comment la mémoire évolue avec le min_gramet max_gramil semble qu'elle dépendrait linéairement de la taille des valeurs de champ et de la plage de minet max. À quel point l'utilisation de quelque chose comme ça est mal vue?
Glen Thompson

Y a-t-il également une raison pour laquelle il ngramexiste un filtre sur un tokenizer? ne pourriez-vous pas simplement l'avoir comme jeton et ensuite appliquer un filtre en minuscules ... index_ngram: { type: "custom", tokenizer: "ngram_tokenizer", filter: [ "lowercase" ] }Je l'ai essayé et il semble donner les mêmes résultats en utilisant l'api de test de l'analyseur
Glen Thompson

2

Si vous souhaitez implémenter la fonctionnalité de saisie semi-automatique, alors Completion Suggester est la solution la plus soignée. Le prochain article de 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 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 contenant SGO les mots hotel, marriot, mercure, munchenet munichressemblerait à ceci:

entrez la description de l'image ici


2

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 recevrez 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.


1

L'utilisation de wilcards (*) empêche le calcul d'un score


1
Pouvez-vous ajouter plus de détails à votre réponse? Fournissez un exemple de code ou une référence à la documentation sur ce que cela fait.
Cray

0

J'utilise ça et j'ai travaillé

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

-6

Ça ne fait rien.

J'ai dû regarder la documentation Lucene. Il semble que je puisse utiliser des jokers! :-)

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

fait l'affaire!


11
Voir la réponse @imotov. L'utilisation de jokers ne va pas du tout évoluer correctement.
Mike Munroe

5
@Idx - Voyez comment votre propre réponse est rejetée. Les votes négatifs représentent la qualité et la pertinence d'une réponse. Pourriez-vous prendre une minute pour accepter la bonne réponse? Au moins les nouveaux utilisateurs vous en seraient reconnaissants.
asyncwait

3
Assez de votes négatifs. OP a précisé quelle est la meilleure réponse à l'heure actuelle. +1 pour avoir partagé ce qui semblait être la meilleure réponse avant que quelqu'un n'en publie une meilleure.
s.Daniel
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.