文章目录
- 前言
- [一、函数评分查询:function_score query](#一、函数评分查询:function_score query)
- [二、boosting query](#二、boosting query)
- [三、文档嵌套类似MySQL连表查询 query](#三、文档嵌套类似MySQL连表查询 query)
- 总结
前言
es的搜索排序默认评分策略只是考虑相关性,如果我们不仅仅需要考虑相关性,还是根据特定的字段来排序,例如餐厅的评分等字段,这是就要用到一下搜索类型。
一、函数评分查询:function_score query
function_score
查询 是用来控制评分过程的终极武器,它允许为每个与主查询匹配的文档应用一个函数,以达到改变甚至完全替换原始查询评分 _score 的目的。
场景:例如想要搜索附近的肯德基,搜索的关键字是肯德基,但是我希望能够将评分较高的肯德基优先展示出来。但是默认的评分策略是没有办法考虑到餐厅评分的,他只是考虑相关性,这个时候可以通过 function_score
query 来实现。
准备两条测试数据:
javascript
PUT blog
{
"mappings": {
"properties": {
"title":{
"type": "text",
"analyzer": "ik_max_word"
},
"votes":{
"type": "integer"
}
}
}
}
PUT blog/_doc/1
{
"title":"Java集合详解",
"votes":100
}
PUT blog/_doc/2
{
"title":"Java多线程详解,Java锁详解",
"votes":10
}
现在搜索标题中包含 java 关键字的文档:
javascript
GET blog/_search
{
"query": {
"match": {
"title": "java"
}
}
}
查询结果如下:
默认情况下,id 为 2 的记录得分较高,因为它的 title 中包含两个 java。
如果我们在查询中,希望能够充分考虑 votes 字段,将 votes 较高的文档优先展示,就可以通过 function_score 来实现。
具体的思路,就是在旧的得分基础上,根据 votes 的数值进行综合运算,重新得出一个新的评分。
具体有几种不同的计算方式:
weight、random_score、script_score、field_value_factor
官网说明:https://www.elastic.co/guide/cn/elasticsearch/guide/current/function-score-query.html
- 1、weight
weight 可以对评分设置权重,就是在旧的评分基础上乘以 weight,他其实无法解决我们上面所说的问题。具体用法如下:
javascript
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"weight": 10
}
]
}
}
}
结果如下:
可以看到,此时的评分,在之前的评分基础上 * 10。
- 2、random_score
random_score
会根据uid
字段进行 hash 运算,生成分数,使用random_score
时可以配置一个种子,如果不配置,默认使用当前时间。
javascript
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"random_score": {}
}
]
}
}
}
- 3、script_score
自定义评分脚本。假设每个文档的最终得分是旧的分数加上votes。查询方式如下:
javascript
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"script_score": {
"script": {
"lang": "painless",
"source": "_score + doc['votes'].value"
}
}
}
]
}
}
}
现在,最终得分是 (oldScore+votes) * oldScore。
如果不想乘以 oldScore,查询方式如下:
javascript
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"script_score": {
"script": {
"lang": "painless",
"source": "_score + doc['votes'].value"
}
}
}
],
"boost_mode": "replace"
}
}
}
通过 boost_mode
参数,可以设置最终的计算方式。该参数还有其他取值:
1、
multiply
:分数相乘2、
sum
:分数相加3、
avg
:求平均数4、
max
:最大分5、
min
:最小分6、
replace
:不进行二次计算
- 4、field_value_factor
这个的功能类似于 script_score
,但是不用自己写脚本。
假设每个文档的最终得分是旧的分数乘以votes。查询方式如下:
javascript
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"field_value_factor": {
"field": "votes"
}
}
]
}
}
}
默认的得分就是oldScore * votes。
还可以利用 es 内置的函数进行一些更复杂的运算:
javascript
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"field_value_factor": {
"field": "votes",
"modifier": "sqrt"
}
}
],
"boost_mode": "replace"
}
}
}
此时,最终的得分是(sqrt(votes))。
modifier 中可以设置内置函数,其他的内置函数还有:
另外还有个参数 factor
,影响因子。字段值先乘以影响因子,然后再进行计算。以 sqrt 为例,计算方式为 sqrt(factor * votes)
:
javascript
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"field_value_factor": {
"field": "votes",
"modifier": "sqrt",
"factor": 10
}
}
],
"boost_mode": "replace"
}
}
}
还有一个参数 max_boost,控制计算结果的范围:
javascript
GET blog/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "java"
}
},
"functions": [
{
"field_value_factor": {
"field": "votes"
}
}
],
"boost_mode": "sum",
"max_boost": 100
}
}
}
max_boost
参数表示 functions 模块中,最终的计算结果上限。如果超过上限,就按照上线计算。
二、boosting query
官网地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html
boosting query 中包含三部分:
1、
positive
:得分不变2、
negative
:降低得分3、
negative_boost
:降低的权重
查询如下:
javascript
GET books/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"name": "java"
}
},
"negative": {
"match": {
"name": "2008"
}
},
"negative_boost": 0.5
}
}
}
结果如下:
可以看到,id 为 86 的文档满足条件,因此它的最终得分在旧的分数上 * 0.5。
三、文档嵌套类似MySQL连表查询 query
关系型数据库中有表的关联关系,在 es 中,我们也有类似的需求,例如订单表和商品表,在 es 中,这样的一对多一般来说有两种方式:
嵌套文档(nested)
、父子文档。
1、嵌套文档
假设:有一个电影文档,每个电影都有演员信息:
javascript
PUT movies
{
"mappings": {
"properties": {
"actors":{
"type": "nested"
}
}
}
}
PUT movies/_doc/1
{
"name":"霸王别姬",
"actors":[
{
"name":"张国荣",
"gender":"男"
},
{
"name":"巩俐",
"gender":"女"
}
]
}
注意
: actors 类型要是 nested。
缺点如下:
查看文档数量:GET _cat/indices?v
查看结果如下:
说明
:这是因为 nested 文档在 es 内部其实也是独立的 lucene 文档,只是在我们查询的时候,es 内部帮我们做了 join 处理,所以最终看起来就像一个独立文档一样。因此这种方案性能并不是特别好。
2、嵌套查询
这个用来查询嵌套文档:
javascript
GET movies/_search
{
"query": {
"nested": {
"path": "actors",
"query": {
"bool": {
"must": [
{
"match": {
"actors.name": "张国荣"
}
},
{
"match": {
"actors.gender": "男"
}
}
]
}
}
}
}
}
3、父子文档
相比于嵌套文档,父子文档主要有如下优势:
1、更新父文档时,不会重新索引子文档
2、创建、修改或者删除子文档时,不会影响父文档或者其他的子文档。
3、子文档可以作为搜索结果独立返回。
例如学生和班级的关系:
javascript
PUT stu_class
{
"mappings": {
"properties": {
"name":{
"type": "keyword"
},
"s_c":{
"type": "join",
"relations":{
"class":"student"
}
}
}
}
}
s_c
表示父子文档关系的名字,可以自定义。join 表示这是一个父子文档。relations 里边,class 这个位置是 parent,student 这个位置是 child。
插入两个父文档:
javascript
PUT stu_class/_doc/1
{
"name":"一班",
"s_c":{
"name":"class"
}
}
PUT stu_class/_doc/2
{
"name":"二班",
"s_c":{
"name":"class"
}
}
再来添加三个子文档:
javascript
PUT stu_class/_doc/3?routing=1
{
"name":"zhangsan",
"s_c":{
"name":"student",
"parent":1
}
}
PUT stu_class/_doc/4?routing=1
{
"name":"lisi",
"s_c":{
"name":"student",
"parent":1
}
}
PUT stu_class/_doc/5?routing=2
{
"name":"wangwu",
"s_c":{
"name":"student",
"parent":2
}
}
首先大家可以看到,子文档都是独立的文档。特别需要注意的地方是,子文档需要和父文档在同一个分片上,所以 routing 关键字的值为父文档的 id。另外,name 属性表明这是一个子文档。
父子文档需要注意的地方:
1、每个索引只能定义一个 join filed
2、父子文档需要在同一个分片上(查询,修改需要routing)
3、可以向一个已经存在的 join filed 上新增关系
4、has_child query
通过子文档查询父文档使用 has_child
query。
javascript
GET stu_class/_search
{
"query": {
"has_child": {
"type": "student",
"query": {
"match": {
"name": "wangwu"
}
}
}
}
}
查询 wangwu 所属的班级。
5、has_parent query
通过父文档查询子文档:
javascript
GET stu_class/_search
{
"query": {
"has_parent": {
"parent_type": "class",
"query": {
"match": {
"name": "二班"
}
}
}
}
}
查询二班的学生。但是大家注意,这种查询没有评分。
可以使用 parent id 查询子文档:
javascript
GET stu_class/_search
{
"query": {
"parent_id":{
"type":"student",
"id":1
}
}
}
通过 parent id 查询,默认情况下使用相关性计算分数。
6、总结
1、普通子对象实现一对多,会损失子文档的边界,子对象之间的属性关系丢失。
2、nested 可以解决第 1 点的问题,但是 nested 有两个缺点:更新主文档的时候要全部更新,不支持子文档属于多个主文档。
3、父子文档解决 1、2 点的问题,但是它主要适用于写多读少的场景。
总结
本文仅简单记录了工作当中很少用到的查询,毕竟大多数时候都是普通查询,更多信息可以参考官网。