多字段、多词匹配,无需查询字符串

22

我希望能够在多个字段中匹配多个单词的搜索,其中每个搜索的单词都包含在任何字段中的任何组合中。但是问题在于我想要避免使用query_string。

curl -X POST "http://localhost:9200/index/document/1" -d '{"id":1,"firstname":"john","middlename":"clark","lastname":"smith"}'
curl -X POST "http://localhost:9200/index/document/2" -d '{"id":2,"firstname":"john","middlename":"paladini","lastname":"miranda"}'

我希望搜索'John Smith'仅匹配文档1。以下查询可以满足我的需求,但我宁愿避免使用query_string,以防用户传递"OR"、"AND"和其他高级参数。

curl -X GET 'http://localhost:9200/index/_search?per_page=10&pretty' -d '{
  "query": {
    "query_string": {
      "query": "john smith",
      "default_operator": "AND",
      "fields": [
        "firstname",
        "lastname",
        "middlename"
      ]
    }
  }
}'

1
我一遍又一遍地回到这个问题。太好了,这是一个永恒的问题! - Henley
4个回答

36
你所需要的是multi-match query,但它的表现并不完全符合你的期望。
比较multi_matchquery_stringvalidate输出的结果。
使用运算符andmulti_match将确保所有术语都存在于至少一个字段中:
curl -XGET 'http://127.0.0.1:9200/_validate/query?pretty=1&explain=true'  -d '
{
   "multi_match" : {
      "operator" : "and",
      "fields" : [
         "firstname",
         "lastname"
      ],
      "query" : "john smith"
   }
}
'

# {
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 1,
#       "total" : 1
#    },
#    "explanations" : [
#       {
#          "index" : "test",
#          "explanation" : "((+lastname:john +lastname:smith) | (+firstname:john +firstname:smith))",
#          "valid" : true
#       }
#    ],
#    "valid" : true
# }

使用默认运算符 ANDquery_string 将检查每个词项是否存在于至少一个字段中:

curl -XGET 'http://127.0.0.1:9200/_validate/query?pretty=1&explain=true'  -d '
{
   "query_string" : {
      "fields" : [
         "firstname",
         "lastname"
      ],
      "query" : "john smith",
      "default_operator" : "AND"
   }
}
'

# {
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 1,
#       "total" : 1
#    },
#    "explanations" : [
#       {
#          "index" : "test",
#          "explanation" : "+(firstname:john | lastname:john) +(firstname:smith | lastname:smith)",
#          "valid" : true
#       }
#    ],
#    "valid" : true
# }

所以您有几个选择来实现您想要的目标:
  1. 在使用query_string之前,预处理搜索词,以删除通配符等内容

  2. 预处理搜索词以提取每个单词,然后为每个单词生成一个multi_match查询

  3. 在映射中使用index_name来为名称字段索引它们的数据到单个字段中,您可以将其用于搜索(类似于自己的自定义all字段):

如下所示:

curl -XPUT 'http://127.0.0.1:9200/test/?pretty=1'  -d '
{
   "mappings" : {
      "test" : {
         "properties" : {
            "firstname" : {
               "index_name" : "name",
               "type" : "string"
            },
            "lastname" : {
               "index_name" : "name",
               "type" : "string"
            }
         }
      }
   }
}
'

curl -XPOST 'http://127.0.0.1:9200/test/test?pretty=1'  -d '
{
   "firstname" : "john",
   "lastname" : "smith"
}
'

curl -XGET 'http://127.0.0.1:9200/test/test/_search?pretty=1'  -d '
{
   "query" : {
      "match" : {
         "name" : {
            "operator" : "and",
            "query" : "john smith"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "firstname" : "john",
#                "lastname" : "smith"
#             },
#             "_score" : 0.2712221,
#             "_index" : "test",
#             "_id" : "VJFU_RWbRNaeHF9wNM8fRA",
#             "_type" : "test"
#          }
#       ],
#       "max_score" : 0.2712221,
#       "total" : 1
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 33
# }

请注意,firstnamelastname不再可以独立搜索。这两个字段的数据已经被索引到name中。
您可以使用multi-fieldspath参数使它们既可以独立搜索,也可以一起搜索,如下所示:
curl -XPUT 'http://127.0.0.1:9200/test/?pretty=1'  -d '
{
   "mappings" : {
      "test" : {
         "properties" : {
            "firstname" : {
               "fields" : {
                  "firstname" : {
                     "type" : "string"
                  },
                  "any_name" : {
                     "type" : "string"
                  }
               },
               "path" : "just_name",
               "type" : "multi_field"
            },
            "lastname" : {
               "fields" : {
                  "any_name" : {
                     "type" : "string"
                  },
                  "lastname" : {
                     "type" : "string"
                  }
               },
               "path" : "just_name",
               "type" : "multi_field"
            }
         }
      }
   }
}
'

curl -XPOST 'http://127.0.0.1:9200/test/test?pretty=1'  -d '
{
   "firstname" : "john",
   "lastname" : "smith"
}
'

搜索 任意名称 字段可行:

curl -XGET 'http://127.0.0.1:9200/test/test/_search?pretty=1'  -d '
{
   "query" : {
      "match" : {
         "any_name" : {
            "operator" : "and",
            "query" : "john smith"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "firstname" : "john",
#                "lastname" : "smith"
#             },
#             "_score" : 0.2712221,
#             "_index" : "test",
#             "_id" : "Xf9qqKt0TpCuyLWioNh-iQ",
#             "_type" : "test"
#          }
#       ],
#       "max_score" : 0.2712221,
#       "total" : 1
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 11
# }

搜索 firstname 中的 john AND smith 不起作用:

curl -XGET 'http://127.0.0.1:9200/test/test/_search?pretty=1'  -d '
{
   "query" : {
      "match" : {
         "firstname" : {
            "operator" : "and",
            "query" : "john smith"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [],
#       "max_score" : null,
#       "total" : 0
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 2
# }

但是仅搜索名字为johnfirstname可以正常工作:

curl -XGET 'http://127.0.0.1:9200/test/test/_search?pretty=1'  -d '
{
   "query" : {
      "match" : {
         "firstname" : {
            "operator" : "and",
            "query" : "john"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "firstname" : "john",
#                "lastname" : "smith"
#             },
#             "_score" : 0.30685282,
#             "_index" : "test",
#             "_id" : "Xf9qqKt0TpCuyLWioNh-iQ",
#             "_type" : "test"
#          }
#       ],
#       "max_score" : 0.30685282,
#       "total" : 1
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 3
# }

我对第一个 multi_match 查询返回结果感到困惑。我猜当你说“所有术语”时,指的是“john”和“smith”。但并不是“john”和“smith”都存在于 first_name 字段中。也不是“john”和“smith”都存在于 last_name 字段中。 - Adam Zerner
@AdamZerner上面的回答还有效吗?因为当我执行multi_match和query_string查询的验证请求时,它们都会产生相同的解释?我正在使用ES 7.10.2。 - Nishikant Tayade
@NishikantTayade 我不确定,抱歉。 - Adam Zerner
@AdamZerner 不用担心!我已经在 ES 论坛上发布了同样的问题,如果我得到答案,我会回来这里告诉你。 - Nishikant Tayade

1

0

现在你可以在multi_match中使用cross_fields类型

GET /_validate/query?explain
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields", 
            "operator":    "and",
            "fields":      [ "firstname", "lastname", "middlename" ]
        }
    }
}

跨字段查询采用以术语为中心的方法。它将所有字段视为一个大字段,并在任何字段中查找每个术语。
需要注意的一件事是,如果要使其最优化运行,所有分析的字段都应该具有相同的分析器(标准、英文等):

为了使跨字段查询类型能够最优化地工作,所有字段都应该具有相同的分析器。共享分析器的字段被分组在一起作为混合字段。

如果您包括具有不同分析链的字段,则会将它们添加到与best_fields相同的查询中。例如,如果我们将标题字段添加到前面的查询中(假设它使用不同的分析器),则说明如下:

(+title:peter +title:smith) ( +blended("peter", fields: [first_name, last_name]) +blended("smith", fields: [first_name, last_name]) )


-1

我认为“match”查询是您要寻找的:

“match”查询系列不经过“查询解析”过程。它不支持字段名称前缀、通配符或其他“高级”功能。因此,它失败的可能性非常小/不存在,并且在分析和运行文本作为查询行为时提供了出色的行为(这通常是文本搜索框所做的)。

http://www.elasticsearch.org/guide/reference/query-dsl/match-query.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接