这里的 Es 数据博主自己上网找的,为了练习 Es 搜索。
搜索分为两个过程:
这里并没有把所有的数据都展示出来,因为默认是有分页功能的。
即 term 查询,就是根据词去查询,查询指定字段中包含给定单词的文档,term 查询不被解析,只有搜索的词和文档中的词精确匹配,才会返回文档。应用场景如:人名、地名等等。
结果:
这里是按照查询出来的分数从高到低排序。最上面是得分最高的,也就是匹配度最高的。
默认返回前 10 条数据,es 中也可以像关系型数据库一样,给一个分页参数:
如果返回的字段比较多,又不需要这么多字段,此时可以指定返回的字段:
这里指定返回 name 字段:
有的文档得分特别低,说明这个文档和我们查询的关键字相关度很低。我们可以设置一个最低分,只有得分超过最低分的文档才会被返回。
match query 会对查询语句进行分词,分词后,如果查询语句中的任何一个词项被匹配,则文档就会被索引到。
前面都是输入词进行查询,这里可以输入一句话来查询了:
效果:
这里是传一句话进去,只要能有一个词能匹配,这条记录就算是相关记录会返回来。
如果想要两个词都包含,那么可以使用 operator 的 and (默认是 or):
match_phrase query 也会对查询的关键字进行分词,但是它分词后有两个特点:
因为这两个词分词之后是挨着的。但现在想要查询十一五和计算机这两个词不要挨在一起,中间可以隔着其他东西。所以这里可以通过 slop 配置中间可以隔多少字符:
效果:
query 是查询的关键字,会被分词器进行分解,分解之后去倒排索引中进行匹配。
slop 是指关键字之间的最小距离,但是注意不是关键之间间隔的字数。文档中的字段被分词器解析之后,解析出来的词项都包含一个 position 字段表示词项的位置,查询短语分词之后 的 position 之间的间隔要满足 slop 的要求。
这个类似于 match_phrase query,只不过这里多了一个通配符,match_phrase_prefix 支持最后一个词项的前缀匹配,但是由于这种匹配方式效率较低,因此大家作为了解即可。
这个查询过程,会自动进行单词匹配,会自动查找以计开始的单词,默认是 50 个,可以自己控制:
match_phrase_prefix 是针对分片级别的查询,假设 max_expansions 为 1,可能返回多个文档,但是只有一个词,这是我们预期的结果。有的时候实际返回结果和我们预期结果并不一致,原因在于这个查询是分片级别的,不同的分片确实只返回了一个词,但是结果可能来自不同的分片,所以最终会看到多个词。
match 查询的升级版,可以指定多个查询域(意思就是查询多个字段):
意思就是更在意 name 中是否有 java ;加到其他字段也就是更在意哪些字段有 java。
query_string 是一种紧密结合 Lucene 的查询方式,在一个查询语句中可以用到 Lucene 的一些查询语法:
这个是 query_string 的升级,可以直接使用 +、|、- 代替 AND、OR、NOT 等。
词项查询。词项查询不会分析查询字符,直接拿查询字符去倒排索引中比对。
范围查询,可以按照日期范围、数字范围等查询。
range query 中的参数主要有四个:
exists query 会返回指定字段中至少有一个非空值的文档:
注意,空字符串也是有值。null 是空值。
前缀查询,效率略低,除非必要,一般不太建议使用。
wildcard query 即通配符查询。支持单字符和多字符通配符:
支持正则表达式查询。
在实际搜索中,有时我们可能会打错字,从而导致搜索不到,在 match query 中,可以通过 fuzziness属性实现模糊查询。
fuzzy query 返回与搜索关键字相似的文档。怎么样就算相似?以LevenShtein 编辑距离为准。编辑距离是指将一个字符变为另一个字符所需要更改字符的次数,更改主要包括四种:
为了找到相似的词,模糊查询会在指定的编辑距离中创建搜索关键词的所有可能变化或者扩展的集合,然后进行搜索匹配:
当我们不关心检索词项的频率(TF)对搜索结果排序的影响时,可以使用 constant_score 将查询语句或者过滤语句包裹起来。
比如:java 出现 10 词跟出现 1 词是一样的,那么就可以这么做:
bool query 可以将任意多个简单查询组装在一起,有四个关键字可供选择,四个关键字所描述的条件可以有一个或者多个。
例如查询 name 属性中必须包含 java,同时书价不在 [0,35] 区间内,info 属性可以包含 程序设计 也可以不包含程序设计:
效果:
这里还涉及到一个关键字, minmum_should_match 参数。
minmum_should_match 参数在 es 官网上称作最小匹配度。在之前学习的 multi_match 或者这里的should 查询中,都可以设置 minmum_should_match 参数。
假设我们要做一次查询,查询 name 中包含 语言程序设计 关键字的文档:
在这个查询过程中,首先会进行分词,分词结果如下:
分词后的 term 会构造成一个 should 的 bool query,每一个 term 都会变成一个 term query 的子句。换句话说,上面的查询和下面的查询等价。
在这两个查询语句中,都是文档只需要包含词项中的任意一项即可,文档就回被返回,在 match 查询中,可以通过 operator 参数设置文档必须匹配所有词项。
如果想匹配一部分词项,就涉及到一个参数,就是 minmum_should_match ,即最小匹配度。即至少匹配多少个词。
50% 表示词项个数的 50%。就是说至少有一般的词量被匹配才会返回。
如下两个查询等价(参数 4 是因为查询关键字分词后有 4 项):
调整数字,数字越多,意味着要匹配的词汇越多,结果越少。
假设现在有两本书:
肉眼观察,第二个的 comtent 语句有两个关键字,第一个只是 title 和 content 各有一个关键字,感觉第二个和查询关键字相似度更高。但是实际查询结果并非这样。
要理解这个原因,我们需要来看下 should query 中的评分策略:
反映到具体的查询中:
前者:
最终结果: (1.1+1.2)*2/2=2.3
后者:
最终结果: 2*1/2=1
在这种查询中,title 和 content 相当于是相互竞争的关系,所以我们需要找到一个最佳匹配字段。
为了解决这一问题,就需要用到 dis_max query(disjunction max query,分离最大化查询):匹配的文档依然返回,但是只将最佳匹配的评分作为查询的评分。(这里就是:跟 title 和 content 都去匹配,谁的得分高就用谁;不在给这两个去算综合的分数):
查询结果如下:
这一次的结果就符合心中的预期了。
在 dis_max query 中,还有一个参数 tie_breaker (取值在0~1),在 dis_max query 中,是完全不考虑其他 query 的分数,只是将最佳匹配的字段的评分返回。但是,有的时候,我们又不得不考虑一下其他 query 的分数,此时,可以通过 tie_breaker 来优化 dis_max query。 tie_breaker 会将其他 query 的分数,乘以 tie_breaker ,然后和分数最高的 query 进行一个综合计算:
如果这里设置回 1,结果又会变得跟原来一样。
场景:例如想要搜索附近的肯德基,搜索的关键字是肯德基,但是我希望能够将评分较高的肯德基优先展示出来。但是默认的评分策略是没有办法考虑到餐厅评分的,他只是考虑相关性,这个时候可以通过 function_score query 来实现。
准备两条测试数据:
现在搜索标题中包含 java 关键字的文档:
查询结果如下:
默认情况下,id 为 2 的记录得分较高,因为他的 title 中包含两个 java。
如果我们在查询中,希望能够充分考虑 votes 字段,将 votes 较高的文档优先展示,就可以通过function_score 来实现。
具体的思路,就是在旧的得分基础上,根据 votes 的数值进行综合运算,重新得出一个新的评分。
具体有几种不同的计算方式:
weight 可以对评分设置权重,就是在旧的评分基础上乘以 weight,他其实无法解决我们上面所说的问题。具体用法如下:
random_score 会根据 uid 字段进行 hash 运算,生成分数,使用 random_score 时可以配置一个种子,如果不配置,默认使用当前时间。
自定义评分脚本。假设每个文档的最终得分是旧的分数加上votes。查询方式如下:
现在,最终得分是 (oldScore+votes)*oldScore 。
如果不想乘以 oldScore,查询方式如下:
通过 boost_mode 参数,可以设置最终的计算方式。该参数还有其他取值:
这个的功能类似于 script_score ,但是不用自己写脚本。
假设每个文档的最终得分是旧的分数乘以votes。查询方式如下:
默认的得分就是 oldScore * votes 。
还可以利用 es 内置的函数进行一些更复杂的运算:
此时,最终的得分是(sqrt(votes))。
modifier 中可以设置内置函数,其他的内置函数还有:
另外还有个参数 factor ,影响因子。字段值先乘以影响因子,然后再进行计算。以 sqrt 为例,计算方式为 sqrt ( factor * votes) :
还有一个参数 max_boost ,控制计算结果的范围:
max_boost 参数表示 functions 模块中,最终的计算结果上限。如果超过上限,就按照上线计算。
boosting query 中包含三部分:
可以看到,id 为 86 的文档满足条件,因此它的最终得分在旧的分数上 *0.5 。
关系型数据库中有表的关联关系,在 es 中,我们也有类似的需求,例如订单表和商品表,在 es 中,这样的一对多一般来说有两种方式:
假设:有一个电影文档,每个电影都有演员信息:
注意 actors 类型要是 nested。
这是因为 nested 文档在 es 内部其实也是独立的 lucene 文档,只是在我们查询的时候,es 内部帮我们做了 join 处理,所以最终看起来就像一个独立文档一样。因此这种方案性能并不是特别好。
相比于嵌套文档,父子文档主要有如下优势:
例如学生和班级的关系:
s_c 表示父子文档关系的名字,可以自定义。join 表示这是一个父子文档。relations 里边,class 这个位置是 parent,student 这个位置是 child。
接下来,插入两个父文档:
再来添加三个子文档:
首先大家可以看到,子文档都是独立的文档。特别需要注意的地方是,子文档需要和父文档在同一个分片上,所以 routing 关键字的值为父文档的 id。另外,name 属性表明这是一个子文档。
父子文档需要注意的地方:
通过子文档查询父文档使用 has_child query。
查询 wangwu 所属的班级。
通过父文档查询子文档:
查询二班的学生。但是大家注意,这种查询没有评分。
可以使用 parent id 查询子文档:
通过 parent id 查询,默认情况下使用相关性计算分数。
整体上来说:
创建一个索引:
PUT geo
{
"mappings": {
"properties": {
"name":{
"type": "keyword"
},
"location":{
"type": "geo_point"
}
}
}
}
准备一个 geo.json 文件:
{"index":{"_index":"geo","_id":1}}
{"name":"西安","location":"34.288991865037524,108.9404296875"}
{"index":{"_index":"geo","_id":2}}
{"name":"北京","location":"39.926588421909436,116.43310546875"}
{"index":{"_index":"geo","_id":3}}
{"name":"上海","location":"31.240985378021307,121.53076171875"}
{"index":{"_index":"geo","_id":4}}
{"name":"天津","location":"39.13006024213511,117.20214843749999"}
{"index":{"_index":"geo","_id":5}}
{"name":"杭州","location":"30.259067203213018,120.21240234375001"}
{"index":{"_index":"geo","_id":6}}
{"name":"武汉","location":"30.581179257386985,114.3017578125"}
{"index":{"_index":"geo","_id":7}}
{"name":"合肥","location":"31.840232667909365,117.20214843749999"}
{"index":{"_index":"geo","_id":8}}
{"name":"重庆","location":"29.592565403314087,106.5673828125"}
最后,执行如下命令,批量导入 geo.json 数据:
curl -XPOST "http://localhost:9200/geo/_bulk?pretty" -H "content-type:application/json" --data-binary @geo.json
可能用到的工具网站(主要用来验证搜索的结果对不对):
http://geojson.io/#map=6/32.741/116.521
给出一个中心点,查询距离该中心点指定范围内的文档:
以(34.288991865037524,108.9404296875) 为圆心,以 600KM 为半径,这个范围内的数据。
效果:
在某一个矩形内的点,通过两个点锁定一个矩形:
以南京经纬度作为矩形的左上角,以舟山经纬度作为矩形的右下角,构造出来的矩形中,包含上海和杭州两个城市。
在某一个多边形范围内的查询。
给定多个点,由多个点组成的多边形中的数据。
上图是三个点,也就是三角形。
geo_shape 用来查询图形,针对 geo_shape ,两个图形之间的关系有:相交、包含、不相交。
新建索引:
然后添加一条线:
接下来查询某一个图形中是否包含该线:
relation 属性表示两个图形的关系:
more_like_this query 可以实现基于内容的推荐,给定一篇文章,可以查询出和该文章相似的内容。
排序很简单,默认是按照查询文档的相关度来排序的,即( _score 字段):
Min Aggregation:统计最小值,用法和 Max Aggregation 基本一致。
Avg Aggregation:统计平均值,用法和 Max Aggregation 基本一致。
Sum Aggregation:求和,用法和 Max Aggregation 基本一致。
cardinality aggregation 用于基数统计。类似于 SQL 中的 distinct count(0):
text 类型是分析型类型,默认是不允许进行聚合操作的,如果相对 text 类型进行聚合操作,需要设置其 fielddata 属性为 true,这种方式虽然可以使 text 类型进行聚合操作,但是无法满足精准聚合,如果需要精准聚合,可以设置字段的子域为 keyword。
接下来就可以查询出版社的总数量:
这种聚合方式可能会不准确。可以将 publish 设置为 keyword 类型或者设置子域为 keyword。
上面那种聚合方式可能会不准确。可以将 publish 设置为 keyword 类型或者设置子域为 keyword:
查询结果如下:
基本统计,一次性返回 count、max、min、avg、sum:
高级统计,比 stats 多出来:平方和、方差、标准差、平均值加减两个标准差的区间:
百分位统计:
效果:
这个百分位统计意思就是:在书的总数量的百分之一的时候,这个书是多少钱,总数量的百分之五的时候,又是多少钱;以此类推。
桶聚合有点像 mysql 的分组查询。
Terms Aggregation 用于分组聚合,例如,统计各个出版社出版的图书总数量:
统计结果如下:
在 terms 分桶的基础上,还可以对每个桶进行指标聚合。
过滤器聚合。可以将符合过滤器中条件的文档分到一个桶中,然后可以求其平均值。
多过滤器聚合。过滤条件可以有多个。
例如查询书名中包含 java 或者 office 的图书的平均价格:
按照范围聚合,在某一个范围内的文档数统计。
例如统计图书价格在 0-50、50-100、100-150、150以上的图书数量:
Range Aggregation 也可以用来统计日期,但是也可以使用 Date Range Aggregation,后者的优势在于可以使用日期表达式。
时间直方图聚合。
空值聚合。
统计所有没有 price 字段的文档:
可以根据父子文档关系进行分桶。
查询子类型为 student 的文档数量:
管道聚合相当于在之前聚合的基础上,再次聚合。
统计每个出版社所出版图书的平均值,然后再统计平均值中的最大值:
统计每个出版社所出版图书的平均值,然后再统计平均值中的最小值。