【问题标题】:Elasticsearch exact matches on analyzed fieldsElasticsearch 精确匹配分析的字段
【发布时间】:2015-05-28 22:09:05
【问题描述】:

有没有办法让 ElasticSearch 识别分析字段上的完全匹配?理想情况下,我想对我的文档进行小写、标记、词干甚至拼音,然后让查询拉出“精确”匹配项。

我的意思是,如果我索引“Hamburger Buns”和“Hamburgers”,它们将被分析为 ["hamburger","bun"] 和 ["hamburger"]。如果我搜索“汉堡包”,它只会返回“汉堡包”文档,因为那是“完全”匹配。

我尝试使用关键字标记器,但这不会阻止单个标记。我需要做些什么来确保令牌的数量相等吗?

我熟悉多字段并使用“not_analyzed”类型,但这比我要寻找的限制更多。我想要精确匹配,后分析。

【问题讨论】:

    标签: elasticsearch


    【解决方案1】:

    将 shingles 分词器与词干提取以及您需要的任何其他内容一起使用。添加一个 token_count 类型的子字段,用于计算字段中的标记数。

    在搜索时,您需要添加一个额外的过滤器来匹配索引中的标记数量与搜索文本中的标记数量。当您执行实际搜索时,您将需要一个额外的步骤,该步骤应该计算搜索字符串中的标记。之所以这样,是因为 shingles 会创建多个标记排列,您需要确保它与搜索文本的大小相匹配。

    对此的尝试,只是为了给你一个想法:

    {
      "settings": {
        "analysis": {
          "filter": {
            "filter_shingle": {
              "type": "shingle",
              "max_shingle_size": 10,
              "min_shingle_size": 2,
              "output_unigrams": true
            },
            "filter_stemmer": {
              "type": "porter_stem",
              "language": "_english_"
            }
          },
          "analyzer": {
            "ShingleAnalyzer": {
              "tokenizer": "standard",
              "filter": [
                "lowercase",
                "snowball",
                "filter_stemmer",
                "filter_shingle"
              ]
            }
          }
        }
      },
      "mappings": {
        "test": {
          "properties": {
            "text": {
              "type": "string",
              "analyzer": "ShingleAnalyzer",
              "fields": {
                "word_count": {
                  "type": "token_count",
                  "store": "yes",
                  "analyzer": "ShingleAnalyzer"
                }
              }
            }
          }
        }
      }
    }
    

    还有查询:

    {
      "query": {
        "filtered": {
          "query": {
            "match_phrase": {
              "text": {
                "query": "HaMbUrGeRs BUN"
              }
            }
          },
          "filter": {
            "term": {
              "text.word_count": "2"
            }
          }
        }
      }
    }
    

    shingles 过滤器在这里很重要,因为它可以创建标记组合。更重要的是,这些是保持顺序或令牌的组合。 Imo,这里最难满足的要求是更改标记(词干,小写等),并且还要组合原始文本。除非您定义自己的“连接”过滤器,否则我认为除了使用 shingles 过滤器之外别无他法。

    但是shingles 存在另一个问题:它创建了不需要的组合。对于像"Hamburgers buns in Los Angeles" 这样的文本,您最终会得到一长串带状疱疹:

              "angeles",
              "buns",
              "buns in",
              "buns in los",
              "buns in los angeles",
              "hamburgers",
              "hamburgers buns",
              "hamburgers buns in",
              "hamburgers buns in los",
              "hamburgers buns in los angeles",
              "in",
              "in los",
              "in los angeles",
              "los",
              "los angeles"
    

    如果您只对那些匹配完全含义的文档感兴趣,则上面的文档仅在您搜索“hamburgers buns in los angeles”时匹配(并且不匹配“any hamburgers洛杉矶的面包”)那么您需要一种方法来过滤那长长的带状疱疹列表。我看到它的方式是使用word_count

    【讨论】:

    • 带状疱疹的用途是什么?
    • 另外,是否有理由同时使用 Porter 和 Snowball 词干分析器?
    • 没有理由。这只是我身边的一个示例,并且能够快速更改它以显示一些真实的代码。重要的部分是shingle 过滤器、token_count 类型字段和查询本身。其余过滤器只是示例:可以取出它们,添加其他东西。
    • 嘿,谢谢。您能解释一下带状过滤器的重要性吗?
    【解决方案2】:

    您可以为此使用multi-fields,并在您的analyzed 字段中拥有一个not_analyzed 子字段(在本例中我们称之为item)。您的映射必须如下所示:

    {
      "yourtype": {
        "properties": {
          "item": {
            "type": "string",
            "fields": {
              "raw": {
                "type": "string",
                "index": "not_analyzed"
              }
            }
          }
        }
      }
    }
    

    通过这种映射,您可以检查分析器如何“查看”每个值 HamburgersHamburger Buns 相对于您的多字段 itemitem.raw

    对于Hamburger

    curl -XGET 'localhost:9200/yourtypes/_analyze?field=item&pretty' -d 'Hamburger'
    {
      "tokens" : [ {
        "token" : "hamburger",
        "start_offset" : 0,
        "end_offset" : 10,
        "type" : "<ALPHANUM>",
        "position" : 1
      } ]
    }
    curl -XGET 'localhost:9200/yourtypes/_analyze?field=item.raw&pretty' -d 'Hamburger'
    {
      "tokens" : [ {
        "token" : "Hamburger",
        "start_offset" : 0,
        "end_offset" : 10,
        "type" : "word",
        "position" : 1
      } ]
    }
    

    对于Hamburger Buns

    curl -XGET 'localhost:9200/yourtypes/_analyze?field=item&pretty' -d 'Hamburger Buns'
    {
      "tokens" : [ {
        "token" : "hamburger",
        "start_offset" : 0,
        "end_offset" : 10,
        "type" : "<ALPHANUM>",
        "position" : 1
      }, {
        "token" : "buns",
        "start_offset" : 11,
        "end_offset" : 15,
        "type" : "<ALPHANUM>",
        "position" : 2
      } ]
    }
    curl -XGET 'localhost:9200/yourtypes/_analyze?field=item.raw&pretty' -d 'Hamburger Buns'
    {
      "tokens" : [ {
        "token" : "Hamburger Buns",
        "start_offset" : 0,
        "end_offset" : 15,
        "type" : "word",
        "position" : 1
      } ]
    }
    

    如您所见,not_analyzed 字段将按照输入时原样被索引。

    现在,让我们索引两个示例文档来说明这一点:

    curl -XPOST localhost:9200/yourtypes/_bulk -d '
    {"index": {"_type": "yourtype", "_id": 1}}
    {"item": "Hamburger"}
    {"index": {"_type": "yourtype", "_id": 2}}
    {"item": "Hamburger Buns"}
    '
    

    最后,为了回答您的问题,如果您想在Hamburger 上进行完全匹配,您可以像这样在您的子字段item.raw 中搜索(注意大小写也必须匹配):

    curl -XPOST localhost:9200/yourtypes/yourtype/_search -d '{
      "query": {
        "term": {
          "item.raw": "Hamburger"
        }
      }
    }'
    

    你会得到:

    {
      ...
      "hits" : {
        "total" : 1,
        "max_score" : 0.30685282,
        "hits" : [ {
          "_index" : "yourtypes",
          "_type" : "yourtype",
          "_id" : "1",
          "_score" : 0.30685282,
          "_source":{"item": "Hamburger"}
        } ]
      }
    }
    

    更新(参见下面的 cmets/讨论和问题重新编辑)

    从 cmets 中获取您的示例并尝试让 HaMbUrGeR BuNs 匹配 Hamburger buns 您可以通过像这样的 match 查询来实现它。

    curl -XPOST localhost:9200/yourtypes/yourtype/_search?pretty -d '{
      "query": {
        "match": {
          "item": {
            "query": "HaMbUrGeR BuNs",
            "operator": "and"
          }
        }
      }
    }'
    

    基于上面相同的两个索引文档将产生

    {
      ...
      "hits" : {
        "total" : 1,
        "max_score" : 0.2712221,
        "hits" : [ {
          "_index" : "yourtypes",
          "_type" : "yourtype",
          "_id" : "2",
          "_score" : 0.2712221,
          "_source":{"item": "Hamburger Buns"}
        } ]
      }
    }
    

    【讨论】:

    • 嘿,感谢您花时间回答这个问题,不幸的是它没有回答我的问题。我知道如果我在 not_analyzed 字段中搜索确切的术语,它将返回正确的结果,但我正在寻找更多的灵活性。例如,如果我搜索“HaMbUrGeRs BuN”,我希望它返回“Hamburger Buns”,而“not_analyzed”不会这样做。这是经过分析的“确切”结果,因为它们匹配。这有意义吗?
    • 是的,这是有道理的。对不起,如果我误解了你的问题。但是,您应该更新您的问题并提及您了解多字段,这不是您要寻找的。​​span>
    • 您更新后的查询将检索“Whole Wheat Hamburger Buns”,如果它也在索引中,对吧?理想情况下,它将只返回完全匹配,类似于过滤器。我想知道这是否真的可以使用 ElasticSearch
    • 不,使用相同的查询搜索“Whole Wheat Hamburger Buns”不会返回任何结果(因为使用“and”运算符)并且使用“or”运算符会返回两个文档。
    【解决方案3】:

    您可以将分析器保持为您所期望的(小写、标记化、词干,...),并使用 query_string 作为主要查询,match_phrase 作为搜索的提升查询。像这样的:

    {
       "bool" : {
          "should" : [
             {
                "query_string" : {
                   "default_field" : "your_field",
                   "default_operator" : "OR",
                   "phrase_slop" : 1,
                   "query" : "Hamburger"
                }
             },
             {
                "match_phrase": {
                   "your_field": {
                      "query": "Hamburger"
                   }
                }
             }
          ]
       }
    }
    

    它将匹配两个文档,并且精确匹配 (match_phrase) 将位于顶部,因为查询匹配两个 should 子句(并获得更高的分数)

    default_operator 设置为 OR,它将帮助查询“Hamburger Buns”(匹配 hamburger OR bun)也匹配文档“Hamburger”。 phrase_slop 设置为 1 以仅匹配距离 = 1 的术语,例如搜索 Hamburger Buns 将不匹配文档 Hamburger Big Buns。您可以根据自己的要求进行调整。

    您可以参考Closer is betterQuery string了解更多详情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-30
      • 2017-03-02
      • 2016-06-23
      • 1970-01-01
      • 2021-12-13
      • 1970-01-01
      相关资源
      最近更新 更多