Como procurar uma parte de uma palavra com o ElasticSearch

128

Recentemente, comecei a usar o ElasticSearch e não consigo fazer com que ele procure parte de uma palavra.

Exemplo: tenho três documentos do meu couchdb indexados no ElasticSearch:

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

Então agora eu quero procurar todos os documentos que contenham "Doe"

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

Isso não retorna nenhum resultado. Mas se eu procurar

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

Ele retorna um documento (John Doeman).

Eu tentei definir diferentes analisadores e filtros diferentes como propriedades do meu índice. Eu também tentei usar uma consulta completa (por exemplo:

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

) Mas nada parece funcionar.

Como posso fazer com que o ElasticSearch encontre John Doeman e Jane Doewoman quando procuro "Doe"?

ATUALIZAR

Tentei usar o tokenizer e o filtro nGram, como Igor propôs, assim:

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

O problema que estou tendo agora é que cada consulta retorna TODOS os documentos. Alguma dica? A documentação do ElasticSearch sobre o uso do nGram não é excelente ...

ldx
fonte
9
Não admira, você habe min / set Ngram máximo para 1, então 1 letra :)
Martin B.

Respostas:

85

Também estou usando o nGram. Eu uso o tokenizer padrão e o nGram apenas como um filtro. Aqui está a minha configuração:

{
  "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 encontrar partes de palavras com até 50 letras. Ajuste o max_gram conforme necessário. Em alemão, as palavras podem ficar muito grandes, então eu defino um valor alto.

roka
fonte
É isso que você obtém das configurações do índice ou é o que você publica na elasticsearch para configurá-lo?
Tomas Jansson
É um POST para configurar o Elasticsearch.
Rok
Não sou firme com as versões atuais do Elasticsearch, mas devo mencioná-lo nos documentos: elastic.co/guide/en/elasticsearch/reference/current/index.html
roka
1
@ JimC Não uso o ElasticSearch há pelo menos 7 anos, por isso não conheço as mudanças atuais do projeto.
roka
63

A pesquisa com curingas iniciais e finais será extremamente lenta em um índice grande. Se você deseja pesquisar por prefixo de palavra, remova o curinga principal. Se você realmente precisar encontrar uma substring no meio de uma palavra, seria melhor usar o ngram tokenizer.

imotov
fonte
14
Igor está certo. Remova pelo menos o * inicial. Para Ngram ElasticSearch exemplo, veja esta essência: gist.github.com/988923
karmi
3
@ karmi: Obrigado pelo seu exemplo completo! Talvez você queira adicionar seu comentário como uma resposta real, é o que o fez funcionar para mim e o que eu gostaria de votar.
Fabian Steeg
54

Eu acho que não há necessidade de alterar nenhum mapeamento. Tente usar query_string , é perfeito. Todos os cenários funcionarão com o analisador padrão padrão:

Temos dados:

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

Cenário 1:

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

Resposta:

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

Cenário 2:

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

Resposta:

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

Cenário 3:

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

Resposta:

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

EDIT - mesma implementação com pesquisa elástica de dados da mola https://stackoverflow.com/a/43579948/2357869

Mais uma explicação de como query_string é melhor que outros https://stackoverflow.com/a/43321606/2357869

Opster Elasticsearch Pro-Vijay
fonte
3
eu acho que isso é o mais fácil
esgi Dendyanri
Sim . Eu implementei no meu projeto.
Opster ElasticSearch Pro-Vijay
Como incluir vários campos para pesquisar?
Shubham A.
tente o seguinte: - {"query": {"query_string": {"fields": ["content", "name"], "query": "this AND that"}}
Opster Elasticsearch Pro-Vijay
verifique este link elastic.co/guide/en/elasticsearch/reference/current/…
Opster Elasticsearch Pro-Vijay
14

sem alterar seus mapeamentos de índice, você poderia fazer uma consulta de prefixo simples que fará pesquisas parciais como você espera

ie

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

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

pythonHelpRequired
fonte
você pode fazer pesquisa de vários campos usando a consulta de prefixo?
batmaci
Obrigado, exatamente o que eu estava procurando! Alguma opinião sobre o impacto no desempenho?
Vingtoft
6

Experimente a solução com está descrita aqui: Pesquisas exatas de substring no 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 o problema de uso do disco e o problema do termo de pesquisa muito longo, são usados ngrams com 8 caracteres e comprimento (configurados com: "max_gram": 8 ). Para pesquisar termos com mais de 8 caracteres, transforme sua pesquisa em uma consulta AND booleana, procurando por cada substring de 8 caracteres distinto nessa sequência. Por exemplo, se um usuário pesquisasse um quintal grande (uma sequência de 10 caracteres), a pesquisa seria:

"arge ya E arge yar E rge yard .

uı6ʎɹnɯ ꞁəıuɐp
fonte
2
link morto, correção de pls
DarkMukke
Eu tenho procurado algo assim por um tempo. Obrigado! Você sabe como as escalas de memória com o min_grame max_gramparece que seria linearmente dependente do tamanho dos valores de campo ea gama de mine max. Quão desaprovado está usando algo assim?
Glen Thompson
Também há alguma razão para que haja ngramum filtro em um tokenizer? poderia não apenas tê-lo como um tokenizer e, em seguida, aplicar um filtro de minúsculas ... index_ngram: { type: "custom", tokenizer: "ngram_tokenizer", filter: [ "lowercase" ] }Eu tentei e parece dar os mesmos resultados usando a API de teste analisador
Glen Thompson
2

Se você deseja implementar a funcionalidade de preenchimento automático, o Completion Suggester é a solução mais interessante. A próxima postagem no blog contém uma descrição muito clara de como isso funciona.

Em duas palavras, é uma estrutura de dados na memória chamada FST que contém sugestões válidas e é otimizada para recuperação rápida e uso de memória. Essencialmente, é apenas um gráfico. Por exemplo, e FST contendo as palavras hotel, marriot, mercure, munchene munichficaria assim:

insira a descrição da imagem aqui

Neshta
fonte
2

você pode usar regexp.

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

se você usar esta consulta:

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

você fornecerá todos os dados cujo nome começa com "J". Considere que deseja receber apenas os dois primeiros registros que terminam com "man" para que você possa usar esta consulta:

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

e se você deseja receber todos os registros que existem em seu nome "m", você pode usar esta consulta:

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

Isso funciona para mim. E espero que minha resposta seja adequada para resolver seu problema.

Ali Moshiri
fonte
1

O uso de wilcards (*) impede o cálculo de uma pontuação

Dardino
fonte
1
Você poderia adicionar mais detalhes à sua resposta? Forneça um código de exemplo ou referência à documentação sobre o que isso faz.
Cray
0

Estou usando isso e trabalhei

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

Deixa pra lá.

Eu tive que olhar para a documentação do Lucene. Parece que eu posso usar curingas! :-)

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

faz o truque!

ldx
fonte
11
Veja a resposta @imotov. O uso de curingas não vai escalar bem.
Mike Munroe
5
@Idx - Veja como sua própria resposta é rebaixada. Os votos negativos representam a qualidade e a relevância de uma resposta. Você poderia poupar um minuto para aceitar a resposta certa? Pelo menos novos usuários ficariam gratos a você.
asyncwait
3
Chega de votos negativos. O OP deixou claro qual é a melhor resposta agora. +1 por compartilhar a que parecia ser a melhor resposta antes que alguém postasse uma melhor.
s.Daniel