【问题标题】:How to search for a part of a word with ElasticSearch如何使用 ElasticSearch 搜索单词的一部分
【发布时间】:2011-09-21 23:07:30
【问题描述】:

我最近开始使用 ElasticSearch,但我似乎无法让它搜索单词的一部分。

示例:我在 ElasticSearch 中索引了我的 couchdb 中的三个文档:

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

所以现在,我要搜索所有包含“Doe”的文档

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

这不会返回任何命中。但是如果我搜索

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

它确实返回一个文档(John Doeman)。

我尝试将不同的分析器和不同的过滤器设置为我的索引的属性。我也尝试过使用完整的查询(例如:

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

) 但似乎没有任何效果。

当我搜索“Doe”时,如何让 ElasticSearch 同时找到 John Doeman 和 Jane Doewoman?

更新

我尝试使用 nGram 标记器和过滤器,就像 Igor 建议的那样,像这样:

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

我现在遇到的问题是每个查询都返回所有文档。 任何指针?关于使用 nGram 的 ElasticSearch 文档不是很好...

【问题讨论】:

  • 难怪,你将 min/max ngram 设置为 1,所以 1 个字母 :)
  • 我真的很惊讶 ES 并没有让这变得更容易。它是 ElasticSearch,而不是 ElasticExactMatchUnlessIDoSomeCeremony

标签: elasticsearch


【解决方案1】:

我也在使用 nGram。我使用标准标记器和 nGram 作为过滤器。这是我的设置:

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

让您找到最多 50 个字母的单词部分。根据需要调整 max_gram。德语单词可以变得非常大,所以我将其设置为高值。

【讨论】:

【解决方案2】:

在大型索引上使用前导通配符和尾随通配符进行搜索会非常缓慢。如果您希望能够按单词前缀搜索,请删除前导通配符。如果你真的需要在一个单词的中间找到一个子字符串,你最好使用 ngram tokenizer。

【讨论】:

  • 伊戈尔是对的。至少删除前导 *.对于 NGram ElasticSearch 示例,请参阅以下要点:gist.github.com/988923
  • @karmi:感谢您的完整示例!也许您想将您的评论添加为实际答案,这就是它对我有用的原因,也是我想要投票的原因。
【解决方案3】:

我认为没有必要更改任何映射。 尝试使用query_string,很完美。所有场景都可以使用默认的标准分析器:

我们有数据:

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

场景 1:

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

回复:

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

场景 2:

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

回复:

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

场景 3:

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

回复:

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

编辑 - 与弹簧数据弹性搜索相同的实现 https://stackoverflow.com/a/43579948/2357869

再解释一下 query_string 如何比其他更好 https://stackoverflow.com/a/43321606/2357869

【讨论】:

  • 我认为这是最简单的
  • 是的。我已经在我的项目中实现了。
  • 试试这个 :-{ "query": { "query_string" : { "fields" : ["content", "name"], "query" : "this AND that" } } }
  • 为什么场景 2 给了 Johan doeman 而不是 Jane Doewoman
  • 这对我有用。谢谢
【解决方案4】:

在不更改索引映射的情况下,您可以执行一个简单的前缀查询,该查询会像您希望的那样进行部分搜索

即。

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

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

【讨论】:

  • 可以使用前缀查询进行多字段搜索吗?
  • 谢谢,正是我想要的!对性能影响有何想法?
【解决方案5】:

尝试此处描述的解决方案:Exact Substring Searches in 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"
                }
            }
        }
    }
}

为了解决磁盘使用问题和搜索词过长问题,使用了短 8 个字符长的 ngrams(配置为:"max_gram": 8)。要搜索超过 8 个字符的术语,请将搜索转换为布尔 AND 查询,查找该字符串中每个不同的 8 个字符子字符串。例如,如果用户搜索 large yard(一个 10 个字符的字符串),则搜索将是:

"arge ya AND arge yar AND rge yard.

【讨论】:

  • 死链接,请修复
  • 我一直在寻找这样的东西。谢谢!你知道内存是如何随min_grammax_gram 扩展的吗?它似乎线性依赖于字段值的大小以及minmax 的范围。使用这样的东西有多不受欢迎?
  • 还有什么理由认为ngram 是对分词器的过滤器?你能不能把它作为一个标记器然后应用一个小写过滤器...index_ngram: { type: "custom", tokenizer: "ngram_tokenizer", filter: [ "lowercase" ] }我试过了,它似乎使用分析器测试 api 给出了相同的结果
【解决方案6】:

虽然有很多答案都专注于解决手头的问题,但并没有过多谈论人们在选择特定答案之前需要做出的各种权衡。因此,让我尝试在这个观点上添加更多细节。

部分搜索现在是一个非常普遍和重要的功能,如果没有正确实施会导致糟糕的用户体验和糟糕的性能,所以首先要了解你的应用程序功能和非功能需求相关我在this detailed SO answer 中谈到的这个功能。

现在有多种方法,例如查询时间、索引时间、完成提示和在您键入数据类型时进行搜索在最新版本的 elasticsarch 中添加。

现在想要快速实施解决方案的人可以使用以下端到端的工作解决方案。

索引映射

{
  "settings": {
    "analysis": {
      "filter": {
        "autocomplete_filter": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 10
        }
      },
      "analyzer": {
        "autocomplete": { 
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "autocomplete_filter"
          ]
        }
      }
    },
    "index.max_ngram_diff" : 10
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "autocomplete", 
        "search_analyzer": "standard" 
      }
    }
  }
}

给定示例文档的索引

{
  "title" : "John Doeman"
  
}

{
  "title" : "Jane Doewoman"
  
}

{
  "title" : "Jimmy Jackal"
  
}

搜索查询

{
    "query": {
        "match": {
            "title": "Doe"
        }
    }
}

返回预期的搜索结果

 "hits": [
            {
                "_index": "6467067",
                "_type": "_doc",
                "_id": "1",
                "_score": 0.76718915,
                "_source": {
                    "title": "John Doeman"
                }
            },
            {
                "_index": "6467067",
                "_type": "_doc",
                "_id": "2",
                "_score": 0.76718915,
                "_source": {
                    "title": "Jane Doewoman"
                }
            }
        ]

【讨论】:

    【解决方案7】:

    如果您想实现自动完成功能,那么Completion Suggester 是最简洁的解决方案。下一个blog post 包含一个非常清晰的描述它是如何工作的。

    简而言之,它是一种称为 FST 的内存数据结构,其中包含有效的建议,并针对快速检索和内存使用进行了优化。本质上,它只是一个图表。例如,包含单词hotelmarriotmercuremunchenmunich 的 FST 看起来像这样:

    【讨论】:

      【解决方案8】:

      你可以使用正则表达式。

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

      如果你使用这个查询:

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

      您将给出其名称以“J”开头的所有数据。考虑您只想接收其名称以“man”结尾的前两条记录,以便您可以使用此查询:

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

      如果你想接收以他们的名字存在的所有记录 "m" ,你可以使用这个查询:

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

      这对我有用。我希望我的回答适合解决你的问题。

      【讨论】:

        【解决方案9】:

        使用通配符 (*) 可防止计算分数

        【讨论】:

        • 您能否在答案中添加更多详细信息?提供示例代码或参考文档以了解其作用。
        【解决方案10】:

        我正在使用它并开始工作

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

        【讨论】:

          【解决方案11】:

          没关系。

          我不得不查看 Lucene 文档。 看来我可以使用通配符! :-)

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

          成功了!

          【讨论】:

          • 见@imotov 的回答。通配符的使用根本无法很好地扩展。
          • @Idx - 看看你自己的答案是如何被否决的。 Downvotes 代表答案的质量和相关性。你能抽出一点时间来接受正确的答案吗?至少新用户会感激你。
          • 足够的反对票。 OP明确了现在最好的答案是什么。 +1 用于在有人发布更好的答案之前分享似乎是最好的答案。
          猜你喜欢
          • 2016-05-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-08-13
          • 2016-08-11
          • 2016-01-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多