【问题标题】:Correct sorting for exact matches and "beginning with" (prefix) in Elasticsearch在 Elasticsearch 中正确排序精确匹配和“开头”(前缀)
【发布时间】:2019-02-12 21:25:16
【问题描述】:

我需要使用 Elasticsearch 改进搜索结果列表。

假设我们有 3 个具有单个字段和内容的文档,如下所示:

  • “苹果”
  • “青苹果”
  • “苹果树”

如果我搜索“apple”,可能会得到这样排序的结果:

  • “青苹果”
  • “苹果树”
  • “苹果”

但我想要的是获得最高分的完全匹配,这里是带有“apple”的文档。

下一个最高分应该是以搜索词开头的条目,这里是“苹果树”,其余排序默认方式。

所以我想拥有它:

  • “苹果”
  • “苹果树”
  • “青苹果”

我已经尝试通过使用 rescore 来实现它:

curl -X GET "http://localhost:9200/my_index_name/_search?size=10&pretty" -H 'Content-Type: application/json' -d'
{
   "query": {
      "query_string": {
          "query": "apple"
      }
   },
   "rescore": {
      "window_size": 500,
      "query": {
         "score_mode": "multiply",
         "rescore_query": {
            "bool": {
               "should": [
                  {
                     "match": {
                        "my_field1": {
                           "query": "apple",
                           "boost": 4
                        }
                     }
                  },
                  {
                     "match": {
                        "my_field1": {
                           "query": "apple*",
                           "boost": 2
                        }
                     }
                  }
               ]
            }
         },
         "query_weight": 0.7,
         "rescore_query_weight": 1.2
      }
   }
}'

但这并不真正有效,因为 Elasticsearch 似乎用空格分隔所有单词。例如,搜索“apple*”也将提供“green apple”。这似乎是重新评分对我不起作用的原因。

可能还有其他字符,例如点“.”、“-”、“;”等等,Elasticsearch 用来拆分和弄乱我的排序。

我还在“rescore_query”中使用“match_phrase”而不是“bool”,但没有成功。

我也试过只匹配一个:

curl -X GET "http://localhost:9200/my_index_name/_search?size=10&pretty" -H 'Content-Type: application/json' -d'
{
   "query": {
      "query_string": {
          "query": "apple"
      }
   },
   "rescore": {
      "window_size": 500,
      "query": {
         "score_mode": "multiply",
         "rescore_query": {
            "bool": {
               "should": [
                  {
                     "match": {
                        "my_field1": {
                           "query": "apple*",
                           "boost": 2
                        }
                     }
                  }
               ]
            }
         },
         "query_weight": 0.7,
         "rescore_query_weight": 1.2
      }
   }
}'

它似乎有效,但我仍然不确定。这是正确的做法吗?

EDIT1:对于其他查询,一个匹配重新评分无法正常工作。

【问题讨论】:

    标签: elasticsearch


    【解决方案1】:

    您需要对分数进行操作的唯一地方是完全匹配,否则术语的位置顺序会为您提供正确的顺序。让我们通过以下方式来理解这一点:

    让我们首先创建如下映射:

    PUT test
    {
      "mappings": {
        "_doc": {
          "properties": {
            "my_field1": {
              "type": "text",
              "analyzer": "whitespace",
              "fields": {
                "keyword": {
                  "type": "keyword"
                }
              }
            }
          }
        }
      }
    }
    

    我使用whitespace 分析器创建了字段my_field1,以确保使用空格作为唯一分隔符来创建标记。其次,我创建了一个名为keyword 的子字段,类型为keywordkeyword 将保存输入字符串的未分析值,我们将使用它进行精确匹配。

    让我们在索引中添加一些文档:

    PUT test/_doc/1
    {
      "my_field1": "apple"
    }
    
    PUT test/_doc/2
    {
      "my_field1": "apple tree"
    }
    
    PUT test/_doc/3
    {
      "my_field1": "green apple"
    }
    

    如果使用下面的查询来搜索术语apple,文档的顺序将是 2,1,3。

    POST test/_doc/_search
    {
      "explain": true,
      "query": {
        "query_string": {
          "query": "apple",
          "fields": [
            "my_field1"
          ]
        }
      }
    }
    

    "explain": true 在上述查询中给出了输出中的分数计算步骤。阅读本文将使您了解文档的评分方式。

    我们需要做的就是提高精确匹配的分数。我们将对字段my_field1.keyword 进行精确匹配。您可能有一个问题,为什么不my_field1。这样做的原因是因为分析了my_field1,当为 3 个文档的输入字符串生成标记时,都将有一个标记(术语)apple(以及其他术语,如果存在,例如tree 用于文档 2和green for doc 3) 存储在此字段上。当我们在该字段上对术语 apple 运行完全匹配时,所有文档都将匹配并对每个文档的分数产生类似的影响,因此分数没有变化。由于只有一个文档具有 applemy_field1.keyword 的精确值,因此该文档(文档 1)将匹配精确查询,我们将对此进行改进。所以查询将是:

    {
      "query": {
        "bool": {
          "should": [
            {
              "query_string": {
                "query": "apple",
                "fields": [
                  "my_field1"
                ]
              }
            },
            {
              "query_string": {
                "query": "\"apple\"",
                "fields": [
                  "my_field1.keyword^2"
                ]
              }
            }
          ]
        }
      }
    }
    

    上述查询的输出:

    {
      "took": 9,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 3,
        "max_score": 1.7260925,
        "hits": [
          {
            "_index": "test3",
            "_type": "_doc",
            "_id": "1",
            "_score": 1.7260925,
            "_source": {
              "my_field1": "apple"
            }
          },
          {
            "_index": "test3",
            "_type": "_doc",
            "_id": "2",
            "_score": 0.6931472,
            "_source": {
              "my_field1": "apple tree"
            }
          },
          {
            "_index": "test3",
            "_type": "_doc",
            "_id": "3",
            "_score": 0.2876821,
            "_source": {
              "my_field1": "green apple"
            }
          }
        ]
      }
    }
    

    【讨论】:

    • Nishant Saini 感谢您的回复。您是否只考虑完全匹配,还是会使用“关键字”类型的嵌套字段也确保“以”开头的单词/短语得分更高?例如。如果我们搜索“apple”(使用您建议的查询),“apple tree”的得分总是高于“green apple”?
    • 这仅满足完全匹配。 Elastic 不会根据术语位置评分。为此,您需要编写自己的自定义逻辑。 This might be helpful 用于编写自定义逻辑/插件。
    • 测试了您的建议并接受了您的回复作为答案,即使我不确定它是否会满足前缀需求,但至少在我的示例中它可以完美运行。谢谢你:)
    • 对于前缀,我建议使用 n-gramedge ngram 标记器来制作自定义分析器。然后使用match phrasematch phrase prefix
    猜你喜欢
    • 2023-03-30
    • 2023-03-22
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    • 2015-10-09
    • 2015-02-18
    • 1970-01-01
    • 2013-03-10
    相关资源
    最近更新 更多