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

ldx
la source
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.

roka
la source
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.

imotov
la source
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

Opster Elasticsearch Pro-Vijay
la source
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
consultez ce lien élastique.co
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

pythonHelpRequired
la source
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 .

uı6ʎɹnɯ ꞁəıuɐp
la source
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

Neshta
la source
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.

Ali Moshiri
la source
1

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

Dardino
la source
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
        }
    }
Saravanavelu
la source
-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!

ldx
la source
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