下述搜索中,如果document中的remark字段包含java或developer词组,都符合搜索条件。

GET /es_db/_search

{
“query”: {
“match”: {
“remark”: “java developer”
}
}
}

remark 是es索引库里面查询到field

如果需要搜索的document中的remark字段,包含java和developer词组,则需要使用下述语法:

GET /es_db/_search
{
“query”: {
“match”: {
“remark”: {
“query”: “java developer”,
“operator”: “and”
}
}
}
}

上述语法中,如果将operator的值改为or。则与第一个案例搜索语法效果一致。默认的ES执行搜索的时候,operator就是or。
如果在搜索的结果document中,需要remark字段中包含多个搜索词条中的一定比例,可以使用下述语法实现搜索。其中minimum_should_match可以使用百分比或固定数字。百分比代表query搜索条件中词条百分比,如果无法整除,向下匹配(如,query条件有3个单词,如果使用百分比提供精准度计算,那么是无法除尽的,如果需要至少匹配两个单词,则需要用67%来进行描述。如果使用66%描述,ES则认为匹配一个单词即可。)。固定数字代表query搜索条件中的词条,至少需要匹配多少个。

当然也可以写数字,写几个就匹配几个,不过建议写百分比,为什么要建议写百分比呢,因为如果内容多的话,你也不知道会匹配多少个,所以写百分比是比较好的方式

68%的意思是至少要匹配两个,写35%就是至少匹配一个

GET /es_db/_search
{
“query”: {
“match”: {
“remark”: {
“query”: “java architect assistant”,
“minimum_should_match”: “68%”
}
}
}
}

下面的意思是至少匹配两个

GET /es_db/_search
{
“query”: {
“match”: {
“remark”: {
“query”: “java architect assistant”,
“minimum_should_match”: 2
}
}
}
}

如果使用should+bool搜索的话,也可以控制搜索条件的匹配度。具体如下:下述案例代表搜索的document中的remark字段中,必须匹配java、developer、assistant三个词条中的至少2个。

这种查询的使用场景是:那么多条件我只需要满足部分匹配即可

GET /es_db/_search
{
“query”: {
“bool”: {
“should”: [
{
“match”: {
“remark”: “java”
}
},
{
“match”: {
“remark”: “developer”
}
},
{
“match”: {
“remark”: “assistant”
}
}
],
“minimum_should_match”: 2
}
}
}

上面这种写法等于

GET /es_db/_search
{
“query”: {
“match”: {
“remark”: {
“query”: “java developer assistant”,
“minimum_should_match”: 2
}
}
}
}

match 的底层转换

其实在ES中,执行match搜索的时候,ES底层通常都会对搜索条件进行底层转换,来实现最终的搜索结果。如:

GET /es_db/_search

{
“query”: {
“match”: {
“remark”: “java developer”
}
}
}
转换后是:
GET /es_db/_search
{
“query”: {
“bool”: {
“should”: [
{
“term”: {
“remark”: “java”
}
},
{
“term”: {
“remark”: {
“value”: “developer”
}
}
}
]
}
}
}

GET /es_db/_search
{
“query”: {
“match”: {
“remark”: {
“query”: “java developer”,
“operator”: “and”
}
}
}
}

转换后是:
GET /es_db/_search
{
“query”: {
“bool”: {
“must”: [
{
“term”: {
“remark”: “java”
}
},
{
“term”: {
“remark”: {
“value”: “developer”
}
}
}
]
}
}
}

GET /es_db/_search
{
“query”: {
“match”: {
“remark”: {
“query”: “java architect assistant”,
“minimum_should_match”: “68%”
}
}
}
}

转换后为:
GET /es_db/_search
{
“query”: {
“bool”: {
“should”: [
{
“term”: {
“remark”: “java”
}
},
{
“term”: {
“remark”: “architect”
}
},
{
“term”: {
“remark”: “assistant”
}
}
],
“minimum_should_match”: 2
}
}
}

建议,如果不怕麻烦,尽量使用转换后的语法执行搜索,效率更高。
如果开发周期短,工作量大,使用简化的写法。

boost权重控制

搜索document中remark字段中包含java的数据,如果remark中包含developer或architect,则包含architect的document优先显示。(就是将architect数据匹配时的相关度分数增加)。

一般用于搜索时相关度排序使用。如:
视频的播放量, 商品的销量, 文章的分享数等等.


boost就是权重的意思,下面的dsl语句是,如果remark字段内容包含了”developer”,那么权重得分值就是1, 如果remark字段内容包含 “architect”,那么权重得分值就是3

GET /es_db/_search
{
“query”: {
“bool”: {
“must”: [
{
“match”: {
“remark”: “java”
}
}
],
“should”: [
{
“match”: {
“remark”: {
“query”: “developer”,
“boost”: 1
}
}
},
{
“match”: {
“remark”: {
“query”: “architect”,
“boost”: 3
}
}
}
]
}
}
}

基于dis_max实现best fields策略进行多字段搜索

best fields策略: 搜索的document中的某一个field,尽可能多的匹配搜索条件。与之相反的是,尽可能多的字段匹配到搜索条件(most fields策略)。如百度搜索使用这种策略。

比如说你输入了一个”hello” most fields的策略是让更多的资源都匹配上”hello”,然后将符合条件的内容取出来.
best fields的策略是看哪个字段最匹配 ,就根据这个字段的打分,将数据取出来.

优点:精确匹配的数据可以尽可能的排列在最前端,且可以通过minimum_should_match来去除长尾数据,避免长尾数据字段对排序结果的影响。
长尾数据比如说我们搜索4个关键词,但很多文档只匹配1个,也显示出来了,这些文档其实不是我们想要的
缺点:相对排序不均匀。
dis_max语法: 直接获取搜索的多条件中的,单条件query相关度分数最高的数据,以这个数据做相关度排序。

什么事best fields呢?
下述的案例中,就是找name字段中rod匹配相关度分数或remark字段中java developer匹配相关度分数,哪个高,就使用哪一个相关度分数进行结果排序。
假如说下面的dsl语句中,根据name分词可能相关度分数更高一些,那么就会根据name的匹配度来做排序.

GET /es_db/_search
{
“query”: {
“dis_max”: {
“queries”: [
{
“match”: {
“name”: “rod”
}
},
{
“match”: {
“remark”: “java developer”
}
}
]
}
}
}

基于tie_breaker参数优化dis_max搜索效果

dis_max是将多个搜索query条件中相关度分数最高的用于结果排序,忽略其他query分数,在某些情况下,可能还需要其他query条件中的相关度介入最终的结果排序,这个时候可以使用tie_breaker参数来优化dis_max搜索。

tie_breaker的作用是可以指定其它字段的打分策略.注意tie_breaker的值设置的时候不能大于1,如果大于1就会报错.

下面的dsl的意思是,如果remark的评分系数高,那么其它字段的权重评分制就乘以tie_breaker设置的值,得出的结果再和remark字段的评分加在一起,计算出总的评分值

下面的就是: 总评分=remark分+(name分*0.5)

GET /es_db/_search
{
“query”: {
“dis_max”: {
“queries”: [
{
“match”: {
“name”: “rod”
}
},
{
“match”: {
“remark”: “java developer”
}
}
],
“tie_breaker”: 0.5
}
}
}

使用multi_match简化dis_max+tie_breaker

这个功能了解即可

ES中相同结果的搜索也可以使用不同的语法语句来实现。不需要特别关注,只要能够实现搜索,就是完成任务!
如:

GET /es_db/_search
{
“query”: {
“dis_max”: {
“queries”: [
{
“match”: {
“name”: “rod”
}
},
{
“match”: {
“remark”: {
“query”: “java developer”,
“boost”: 2,
“minimum_should_match”: 2
}
}
}
],
“tie_breaker”: 0.5
}
}
}

使用multi_match语法为:其中type常用的有best_fields和most_fields。^n代表权重,相当于”boost”:n。

GET /es_db/_search
{
“query”: {
“multi_match”: {
“query”: “rod java developer”,
“fields”: [
“name”,
“remark^2”
],
“type”: “best_fields”,
“tie_breaker”: 0.5,
“minimum_should_match”: “50%”
}
}
}

cross fields搜索

cross fields是多字段查询,cross fields和 most fields 是差不多的,cross fields在 most fields 基础之上带一些额外的operator参数.

cross fields是什么 : 用户输入的关键字,分部在一个索引库里面的多个fields中,使用这种唯一标识搜索数据就称为cross fields搜索。
如:人名可以分为姓和名,地址可以分为省、市、区县、街道等。那么使用人名或地址来搜索document,就称为cross fields搜索。

实现这种搜索,一般都是使用most fields搜索策略。因为这就不是一个field的问题。

Cross fields搜索策略,是从多个字段中搜索条件数据。默认情况下,和most fields搜索的逻辑是一致的,计算相关度分数是和best fields策略一致的。一般来说,如果使用cross fields搜索策略,那么都会携带一个额外的参数operator。用来标记搜索条件如何在多个字段中匹配。
当然,在ES中也有cross fields搜索策略。具体语法如下:

  1. GET /es_db/_search
  2. {
  3. "query": {
  4. "multi_match": {
  5. "query": "java developer",
  6. "fields": [
  7. "name",
  8. "remark"
  9. ],
  10. "type": "cross_fields",
  11. "operator": "and"
  12. }
  13. }
  14. }

上面dsl语法代表的是,因为配置了 “operator”: “and” 了,所以搜索条件中的java必须在name或remark字段中匹配,developer也必须在name或remark字段中匹配。

operator 参数可以指定fields列表里面多个字段的关系, “operator”: “and” ,也可以是 “operator”: “or” ,可能还有其它的,具体百度一下.

most field策略问题:most fields策略是尽可能匹配更多的字段,所以会导致精确搜索结果排序问题。又因为cross fields搜索,不能使用minimum_should_match来去除长尾数据。
所以在使用most fields和cross fields策略搜索数据的时候,都有不同的缺陷。所以商业项目开发中,都推荐使用best fields策略实现搜索。

copy_to组合fields

我在京东搜索框输入一个关键字 “手机”,然后点击搜索按钮的时候,那么我是只匹配标题,还是既匹配标题又匹配内容呢?别的字段要不要匹配呢?

京东中,如果在搜索框中输入“手机”,点击搜索,那么是在商品的类型名称、商品的名称、商品的卖点、商品的描述等字段中,哪一个字段内进行数据的匹配?如果使用某一个字段做搜索不合适,那么使用_all做搜索是否合适?也不合适,因为_all字段中可能包含图片,价格等字段。
假设,有一个字段,其中的内容包括(但不限于):商品类型名称、商品名称、商品卖点等字段的数据内容。是否可以在这个特殊的字段上进行数据搜索匹配?

{
“category_name”: “手机”,
“product_name”: “一加6T手机”,
“price”: 568800,
“sell_point”: “国产最好的Android手机”,
“tags”: [
“8G+128G”,
“256G可扩展”
],
“color”: “红色”,
“keyword”: “手机 一加6T手机 国产最好的Android手机”
}

copy_to : 就是将多个字段,复制到一个字段中,实现一个多字段组合。copy_to可以解决cross fields搜索问题,在商业项目中,也用于解决搜索条件默认字段问题。
如果需要使用copy_to语法,则需要在定义index的时候,手工指定mapping映射策略。
copy_to语法:

PUT /es_db/_mapping
{
    "properties": {
        "provice": {
            "type": "text",
            "analyzer": "standard",
            "copy_to": "address"
        },
        "city": {
            "type": "text",
            "analyzer": "standard",
            "copy_to": "address"
        },
        "street": {
            "type": "text",
            "analyzer": "standard",
            "copy_to": "address"
        },
        "address": {
            "type": "text",
            "analyzer": "standard"
        }
    }
}

上面mapping定义中,是新增了4个字段,分别是provice、city、street、address,其中provice、city、street三个字段的值,会自动复制到address字段中,实现一个字段的组合。
在搜索的时候,比如说我想搜索一个关键字,这个关键字只要在provice、city、street这三个字段里面的其中任意一个字段存在,就返回给用户.
那么就直接查询address字段即可,因为provice、city、street这三个字段在定义的时候都”copy_to”: “address” 上了,

从而避免most fields策略导致的问题。在维护数据的时候,不需对address字段特殊的维护。因为address字段是一个组合字段,是由ES自动维护的。类似java代码中的推导属性。在存储的时候,未必存在,但是在逻辑上是一定存在的,因为address是由3个物理存在的属性province、city、street组成的。

近似匹配

前文都是精确匹配。如doc中有数据java assistant,那么搜索jave是搜索不到数据的。因为jave单词在doc中是不存在的。
如果搜索的语法是:

GET _search
{
“query”: {
“match”: {
“name”: “jave”
}
}
}

如果需要的结果是有特殊要求,如:hello world必须是一个完整的短语,不可分割;或document中的field内,包含的hello和world单词,且两个单词之间离的越近,相关度分数越高。那么这种特殊要求的搜索就是近似搜索。包括hell搜索条件在hello world数据中搜索,包括h搜索提示等都数据近似搜索的一部分。
如何上述特殊要求的搜索,使用match搜索语法就无法实现了。

match phrase

短语搜索。就是搜索条件不分词。代表搜索条件不可分割。
如果hello world是一个不可分割的短语,我们可以使用前文学过的短语搜索match phrase来实现。语法如下:

GET _search
{
“query”: {
“match_phrase”: {
“remark”: “java assistant”
}
}
}


-1)、 match phrase原理 — term position
ES是如何实现match phrase短语搜索的?其实在ES中,使用match phrase做搜索的时候,也是和match类似,首先对搜索条件进行分词-analyze。将搜索条件拆分成hello和world。既然是分词后再搜索,ES是如何实现短语搜索的?
这里涉及到了倒排索引的建立过程。在倒排索引建立的时候,ES会先对document数据进行分词,如:

GET _analyze
{
“text”: “hello world, java spark”,
“analyzer”: “standard”
}

分词的结果是:
{
“tokens”: [
{
“token”: “hello”,
“start_offset”: 0,
“end_offset”: 5,
“type”: ““,
“position”: 0
},
{
“token”: “world”,
“start_offset”: 6,
“end_offset”: 11,
“type”: ““,
“position”: 1
},
{
“token”: “java”,
“start_offset”: 13,
“end_offset”: 17,
“type”: ““,
“position”: 2
},
{
“token”: “spark”,
“start_offset”: 18,
“end_offset”: 23,
“type”: ““,
“position”: 3
}
]
}

从上述结果中,可以看到。ES在做分词的时候,除了将数据切分外,还会保留一个position。position代表的是这个词在整个数据中的下标。当ES执行match phrase搜索的时候,首先将搜索条件hello world分词为hello和world。然后在倒排索引中检索数据,如果hello和world都在某个document的某个field出现时,那么检查这两个匹配到的单词的position是否是连续的,如果是连续的,代表匹配成功,如果是不连续的,则匹配失败。

-2). match phrase搜索参数 — slop
在做搜索操作的是,如果搜索参数是hello spark。而ES中存储的数据是hello world, java spark。那么使用match phrase则无法搜索到。在这个时候,可以使用match来解决这个问题。但是,当我们需要在搜索的结果中,做一个特殊的要求:hello和spark两个单词距离越近,document在结果集合中排序越靠前,这个时候再使用match则未必能得到想要的结果。
ES的搜索中,对match phrase提供了参数slop。slop代表match phrase短语搜索的时候,单词最多移动多少次,可以实现数据匹配。在所有匹配结果中,多个单词距离越近,相关度评分越高,排序越靠前。
这种使用slop参数的match phrase搜索,就称为近似匹配(proximity search)
如:
数据为: hello world, java spark
搜索为: match phrase : hello spark。
slop为: 3 (代表单词最多移动3次。)
执行短语搜索的时候,将条件hello spark分词为hello和spark两个单词。并且连续。
hello spark
接下来,可以根据slop参数执行单词的移动。
下标 : 0 1 2 3
doc : hello world java spark
搜索 : hello spark
移动1: hello spark
移动2: hello spark
匹配成功,不需要移动第三次即可匹配。

如果:
数据为: hello world, java spark
搜索为: match phrase : spark hello。
slop为: 5 (代表单词最多移动5次。)
执行短语搜索的时候,将条件hello spark分词为hello和spark两个单词。并且连续。
spark hello
接下来,可以根据slop参数执行单词的移动。
下标 : 0 1 2 3
doc : hello world java spark
搜索 : spark hello
移动1: spark/hello
移动2: hello spark
移动3: hello spark
移动4: hello spark
匹配成功,不需要移动第五次即可匹配。

如果当slop移动次数使用完毕,还没有匹配成功,则无搜索结果。如果使用中文分词,则移动次数更加复杂,因为中文词语有重叠情况,很难计算具体次数,需要多次尝试才行。

测试案例:
英文:

GET _analyze
{
“text”: “hello world, java spark”,
“analyzer”: “standard”
}

POST /test_a/_doc/3
{
“f”: “hello world, java spark”
}

GET /test_a/_search
{
“query”: {
“match_phrase”: {
“f”: {
“query”: “hello spark”,
“slop”: 2
}
}
}
}

GET /test_a/_search
{
“query”: {
“match_phrase”: {
“f”: {
“query”: “spark hello”,
“slop”: 4
}
}
}
}

中文:

GET _analyze
{
“text”: “中国,一个世界上最强的国家”,
“analyzer”: “ik_max_word”
}

POST /test_a/_doc/1
{
“f”: “中国,一个世界上最强的国家”
}

GET /test_a/_search
{
“query”: {
“match_phrase”: {
“f”: {
“query”: “中国最强”,
“slop”: 5
}
}
}
}

GET /test_a/_search
{
“query”: {
“match_phrase”: {
“f”: {
“query”: “最强中国”,
“slop”: 9
}
}
}
}