排序与分页
json
localhost:9200/book/_search get请求
{
"query":{
"match_all": {}
},
"sort": [
{
"price": {
"order": "desc"
}
}
],
"from":0,
"size":3
}
简化body
{
"query": {
"bool": {
"should":[
{"match":{"_index":"book"}}
]
}
},
"sort":[{"price":"desc"}],
"from":0,
"size":5
}
以上是浅分页~即每次只读取指定数据量的的数据,这样我们在分页的时候要获得下一页就得再次去读取,这就会造成频繁请求,显然是不符合我们设计程序的要求的;
~由于当前PC设计时读取一次数据大小为4K,即一次会多读取一些数据(PC认为这些相邻的数据也可能时我们需要的)
下面我们就会用到深分页
深分页
http://localhost:9200/book/_search?scroll=5m
get 请求
json
{
"query": {
"match_all": {}
},
"size": 1,
"from": 0,
"sort": [
{
"price": "asc"
}
]
}
请求中的scroll=5m 表示当前查询上下文保留时间 5min
返回结果:
json
{
"_scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFm9JWUxmMHVMUmo2Q1FtVV9LNGdXQVEAAAAAAAAoyRZDUFlvWlFNR1NCYU9kcTg2bnpBY2hn",
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "book",
"_type": "_doc",
"_id": "4",
"_score": null,
"_source": {
"id": 4,
"title": "中华上下五千年",
"price": 100
},
"sort": [
100.0
]
}
]
}
}
获取下一页:
json
{
"scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFm9JWUxmMHVMUmo2Q1FtVV9LNGdXQVEAAAAAAAAoyRZDUFlvWlFNR1NCYU9kcTg2bnpBY2hn",
"scroll": "5m"
}
使用上面的请求返回的结果中包含一个 scroll_id,这个 ID 可以被传递给 scroll API 来检索下一个批次的结果。
返回:
json
{
"_scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFm9JWUxmMHVMUmo2Q1FtVV9LNGdXQVEAAAAAAAAoyRZDUFlvWlFNR1NCYU9kcTg2bnpBY2hn",
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "book",
"_type": "_doc",
"_id": "5",
"_score": null,
"_source": {
"id": 5,
"title": "三国志2",
"price": 100
},
"sort": [
100.0
]
}
]
}
}
GET 或者 POST 可以使用
URL不应该包含 index 或者 type 名字------这些都指定在了原始的 search 请求中。
scroll 参数告诉 Elasticsearch 保持搜索的上下文等待另一个 1m
scroll_id 参数
每次对 scroll API 的调用返回了结果的下一个批次直到没有更多的结果返回,也就是直到 hits 数组空了。
注意:初始搜索请求和每个后续滚动请求返回一个新的 _scroll_id,只有最近的 _scroll_id 才能被使用。
即之前的scroll_id失效,需要重新获取新的scroll_id
查询时候要考虑分词
- 搜索的时候要考虑分词
原始数据:
json
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "book",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"id" : 4,
"title" : "中华上下五千年",
"price" : 100
}
},
{
"_index" : "book",
"_type" : "_doc",
"_id" : "5",
"_score" : 1.0,
"_source" : {
"id" : 5,
"title" : "三国志2",
"price" : 100
}
},
{
"_index" : "book",
"_type" : "_doc",
"_id" : "10",
"_score" : 1.0,
"_source" : {
"id" : 10,
"title" : "陈寿写的三国志",
"price" : 199
}
}
]
}
}
查询
json
GET /book/_search
{
"query": {"match": {
"title": "三国志"
}}
}
结果:
json
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 5.8973975,
"hits" : [
{
"_index" : "book",
"_type" : "_doc",
"_id" : "10",
"_score" : 5.8973975,
"_source" : {
"id" : 10,
"title" : "陈寿写的三国志",
"price" : 199
}
},
{
"_index" : "book",
"_type" : "_doc",
"_id" : "18",
"_score" : 1.5520117,
"_source" : {
"id" : 18,
"title" : "三个国家的孩子拥有不同的状志",
"price" : 199
}
},
{
"_index" : "book",
"_type" : "_doc",
"_id" : "5",
"_score" : 1.0465285,
"_source" : {
"id" : 5,
"title" : "三国志2",
"price" : 100
}
}
]
}
}
为什么会查到关于三国志的全部数据?
我们首先得要知道分词如果没有为索引指定分词,那么默认使用标准分词器standard
standard分词器对 三国志进行分词
json
{
"tokens" : [
{
"token" : "三",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "国",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "志",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
}
]
}
我们在查询三国志的时候
standard分词 三 国 志 ,因此只要是title包含 这三个中的一个就会被检索到
我们添加数据到book中
json
PUT /book/_doc/20
{
"id" : 20,
"title" : "三",
"price" : 199
}
PUT /book/_doc/21
{
"id" : 21,
"title" : "国",
"price" : 199
}
再次查询时,hui'fa按刚刚添加的 三 国 数据也被检索到了,这显然并不是目前我们想要的
我们需要使用别的分词器,比如ik分词器对中文进行分词;
但是由于我们创建时没有指定分词器,因此es使用了默认的分词器,所以我们需要修改分词器
修改分词器:
在修改索引的分词器的时候我们先要关闭索引,添加完成后再打开索引
关闭索引:
json
localhost:9200/book/_close
返回结果:
{
"acknowledged": true,
"shards_acknowledged": true,
"indices": {
"book": {
"closed": true
}
}
}
然后我们再次查询/添加/修改book索引的时候:
json
{
"error" : {
"root_cause" : [
{
"type" : "index_closed_exception",
"reason" : "closed",
"index_uuid" : "QUzu_Db5Q-6Rlv2e-Dd36w",
"index" : "book"
}
],
"type" : "index_closed_exception",
"reason" : "closed",
"index_uuid" : "QUzu_Db5Q-6Rlv2e-Dd36w",
"index" : "book"
},
"status" : 400
}
修改分词器
json
localhost:9200/book/_settings put
{
"analysis":{
"analyzer":{
"ik_filter":{
"type":"ik_max_word"
}
}
}
}
打开索引:
json
localhost:9200/book/_open post
查看索引设置
json
localhost:9200/book/_settings get
返回结果
{
"book": {
"settings": {
"index": {
"routing": {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards": "1",
"provided_name": "book",
"max_result_window": "10000",
"creation_date": "1717588143904",
"analysis": {
"analyzer": {
"ik_filter": {
"type": "ik_max_word"
}
}
},
"number_of_replicas": "1",
"uuid": "QUzu_Db5Q-6Rlv2e-Dd36w",
"version": {
"created": "7172199"
}
}
}
}
}
再次请求时会发现依然不满足要求
json
GET /book/_search
{
"query": {
"match": {
"title":"三国志"
}}
}
别着急,我们先看看ik分词器ik_max_word分词器对三国志是怎么分词的
json
{
"tokens" : [
{
"token" : "三国志",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "三国",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "三",
"start_offset" : 0,
"end_offset" : 1,
"type" : "TYPE_CNUM",
"position" : 2
},
{
"token" : "国",
"start_offset" : 1,
"end_offset" : 2,
"type" : "CN_CHAR",
"position" : 3
},
{
"token" : "志",
"start_offset" : 2,
"end_offset" : 3,
"type" : "CN_CHAR",
"position" : 4
}
]
}
看样子仅仅通过分词器是不能满足我们的需求的,还需要做下面的修改
json
GET /book/_search
{
"query": {
"match": {
"title": {
"query": "三国志",
"minimum_should_match": 3
}
}}
}
之后:
json
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 1.4711425,
"hits": [
{
"_index": "book",
"_type": "_doc",
"_id": "5",
"_score": 1.4711425,
"_source": {
"id": 5,
"title": "三国志2",
"price": 100
}
},
{
"_index": "book",
"_type": "_doc",
"_id": "10",
"_score": 1.1695743,
"_source": {
"id": 10,
"title": "陈寿写的三国志",
"price": 199
}
},
{
"_index": "book",
"_type": "_doc",
"_id": "18",
"_score": 0.79115736,
"_source": {
"id": 18,
"title": "三个国家的孩子拥有不同的状志",
"price": 199
}
}
]
}
}
minimum_should_match 最小匹配分词数
还有一种方式是再查询的时候指定拆分的行为:
json
GET /book/_search
{
"query": {
"match": {
"title": {
"query": "三国志",
"operator": "and"
}
}}
}
GET /book/_search
{
"query": {
"match": {
"title": {
"query": "三国志",
"operator": "or"
}
}}
}
operator 拆分行为 是and 还是or ~效果自己可以试一下
如果指定operator为and,那么查询的时候默拆分行为默认为or
以上是我们在不了解es的情况下来获得我们需要的结果,但是es本身提供了很多高级的查询,我们来试试
比如 match_phase
json
GET /book/_search
{
"query":{"match_phrase": {
"title": "三国志"
}}
}
返回结果是三 国 志 这三个组合在一块的搜索结果
假设我们需要查"三志",那又会出现什么结果呢?
json
GET /book/_search
{
"query":{"match_phrase": {
"title": "三志"
}}
}
结果是查不到数据,因为分词的时候没有出现三志这个组合;
我们在改造一下,查询title的时候
json
GET /book/_search
{
"query":{
"match_phrase": {
"title":{
"query":"三志",
"slop": 1
}
}
}
}
再查看检索结果:
json
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.7056085,
"hits" : [
{
"_index" : "book",
"_type" : "_doc",
"_id" : "5",
"_score" : 0.7056085,
"_source" : {
"id" : 5,
"title" : "三国志2",
"price" : 100
}
},
{
"_index" : "book",
"_type" : "_doc",
"_id" : "10",
"_score" : 0.5246588,
"_source" : {
"id" : 10,
"title" : "陈寿写的三国志",
"price" : 199
}
}
]
}
}
slop 允许的最大偏移量,即允许title中"三"和"志"之间的距离为多少
当我们查询时候slop用于指定查询的时候分词间允许的最大偏移量;
有时候我们查询的时候只想找到最相关的一条,
multi_match
multi_match 顾名思义就是提供在多个字段上查询的意思
json
{
"query": {
"multi_match": {
"query": "三国",
"fields": [
"title",
"desc"
]
}
},
"form":0,
"size":1
}
这里我们查询了title字段,并按照相关度倒序排序,取相关度最高的
multi_match查询时有很多参数:
query
来自用户输入的查询短语
fields
数组,默认支持最大长度1024,可以单独为任意字段设置相关度权重,支持通配符;fields可以为空,为空时会取mapping阶段配置的所有支持term查询的filed组合在一起进行查询
type 定义内部大打分方式
1,best_fields 按照match检索,所有字段单独计算得分并取最高分的field为最终_score,虽然是默认值,但不建议使用,数据量上来后查询性能会下降
2,most_fields 按照match检索,融合所有field得分为最终_score
3,cross_fields 将fields中的所有字段融合成一个大字段进行match检索,此时要求所有字段使用相同分析器
4,phrase 按照match_phrase检索,默认slop为0,执行短语精确匹配,所以即便设置 minimum_should_match 也无效; 取最高字段得分
5,phrase_prefix 按照match_phrase_prefix检索,滑动步长slop默认为0;取最高字段得分
6,bool_prefix 按照match_bool_prefix检索
当best_fields、most_fields与operator 或 minimum_should_match 参数连用时会要求用户输入词必须在任意单一字段上完全满足的文档才会出现;
json
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": 0.8321978,
"hits": [
{
"_index": "book",
"_type": "_doc",
"_id": "5",
"_score": 0.8321978,
"_source": {
"id": 5,
"title": "三国志2",
"price": 100
}
}
]
}
}
query_string
json
localhost:9200/book/_search get请求
{
"query":{
"query_string":{
"query":"陈寿 OR 三国志"
}
}
}
匹配陈寿 三国志 的文档文档中所有的字段 OR是或,AND是且
查询指定字段:
json
{
"query":{
"query_string":{
"fields":["title","desc"],
"query":"陈寿 AND 三国志"
}
}
}
返回:
json
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
简单字符串查询 simple_query_string
用法跟query_string 差不多,只不过AND 用+ 代替,| 代替OR -代替NOT
json
localhost:9200/book/_search get
{
"query":{
"simple_query_string":{
"fields":["title","desc"],
"query":"陈寿+三国志"
}
}
}
关键字查询:
关键词查询 TermTerm 用来使用关键词旬(精确匹配 ),
还可以用来查询没有被进行分词的数据类型。
Term 是表达浯意的最小单位,搜索和利用统计浯言模型讲行自然浯言处理都需要处理 Term 。 match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找,而 term 会直接对关键词进行查找。一般模糊查找的时候,多用 match, 而精确查找时可以使用 term 。
ES 中默认使用分词器为标准分词器( Standardard), 标准分词器对于英文单词分词,
对于中文单字分词,在 ES 的 Mapping Type 中 keyword , date , integer, long , double , boolean or ip 这些类型不分词,只有 te×t 类型分词。
一个简单的关键字查询:
json
{
"query": {
"term": {
"title": {
"value":"三国志2"
}
}
}
}
结果是什么也查询不到,这是为啥啊呢
json
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
按道理,三国志2 拆分 三 三国 三国志 应该能查到数据才对,但是为什么查不到呢?按道理我们也应该查询到呀;
我们看一下索引的映射:
json
GET /book/_mapping
结果:
{
"book": {
"mappings": {
"properties": {
"id": {
"type": "long"
},
"price": {
"type": "float"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
看到title中有type 为 keyword,我们查询的时候查询关键字就可以了,此时terms就不会在去分词查询了:
json
{
"query": {
"term": {
"title.keyword": {
"value":"三国志2"
}
}
}
}
结果:
json
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.6739764,
"hits" : [
{
"_index" : "book",
"_type" : "_doc",
"_id" : "5",
"_score" : 1.6739764,
"_source" : {
"id" : 5,
"title" : "三国志2",
"price" : 100
}
}
]
}
}
在 ES 中,对输入不做分词。对输入作为一个整体,在倒排索引中查找准确的词项,并且使用相关度算分公式为每个包含该词项的文档进行相关度算分。
这里有一个大坑,就是...比如下面这个例子:
我们还是在索引book中添加数据
json
{
"id":12,
"title":"JAVA",
"pricr":99
}
添加完之后使用term查询
我们发现是查不到的:
json
{
"query":{
"term":{
"title":"JAVA"
}
}
}
结果:
```json
{
"took": 682,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
这是为什么呢?
我们来看一下 分词器对JAVA是怎么分词的:
json
localhost:9200/_analyze post
{
"analyzer":"ik_max_word",
"text":"JAVA"
}
返回结果:
{
"tokens": [
{
"token": "java",
"start_offset": 0,
"end_offset": 4,
"type": "ENGLISH",
"position": 0
}
]
}
可以看到,JAVA被分词器转换成小写的了,所以我们查询JAVA的时候要使用java,否则查不到
json
GET /book/_search
{
"query":{
"term": {
"title": {
"value": "java"
}
}
}
}
结果:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 2.6464858,
"hits" : [
{
"_index" : "book",
"_type" : "_doc",
"_id" : "12",
"_score" : 2.6464858,
"_source" : {
"id" : 12,
"title" : "JAVA",
"pricr" : 99
}
}
]
}
}
这样会有一个问题就是如果查询java的时候,返回的结果可能会有一下集中Java,JAvA等等多种情况,此时如果要精准匹配JAVA那么就需要制定 keyword
keyword 是 ES 中一个特殊的类型,它不会进行分词,而是直接使用原始值进行匹配。
json
GET /book/_search
{
"query":{
"term": {
"title.keyword": {
"value": "JAVA"
}
}
}
}
ES优化1
ES优化~精确查询的时候避免算相关度
上面的查询我们看到了有一个_score 字段,这个字段用于表示查询到的这一个数据与我们想要查询的数据的相关度,但是当我们查询的时候,我们希望的是精确查询,但是ES默认是会进行相关度算分的,这个相关度计算是需要消耗算力的,那么怎么在精确查询的时候不使用_score算分呢?
1,可以通过Constant Score Query 来实现,Constant Score Query 是 ES 提供的一个查询类型,它可以将查询条件包装成一个常量分数,然后返回匹配到的所有文档,而不进行相关度算分。
2,filter 可以有效利用缓存
示例:
json
GET /book/_search
{"query":{
"constant_score":{
"filter":{
"term":{
"title.keyword":"JAVA"
}
}
}
}}
这个查询和上面查询的效果是一样的,但是这个查询不会进行相关度算分(默认相关度为1),所以性能会好很多
ES优化2
我们在查询 bool 数字等类型的时候可以使用term查询
比如
json
{
"query": {
"term": {
"price": 100
}
}
}
这里查询bool,数字等类型的时候就不用特别指定 constant_score了;
term--->
关键词查询Term
被进行分词的数据类型。Term是表达语意的最小单位,搜索和利用统计语言Term用来使用关键词查询(精确匹配),还可以用来查询没有型进行自然语言处理都需要处理Term。match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找,而term会直接对关键词进行查找。一般模糊查找的时候,多用match,而精确查找可以使用term。
),标准分词器对于英文单词分词,对于中文单字分词,·ES中默认使用分词器为标准分词器(StandardAnalyze,double,boolean orip 这些类型不分词,只有text类型分词在ES的MappingType 中 keyword , date ,integer, long
关于term查询为什么会查不到数据~首先term精确查询的时候是不分词的,但是存的时候的倒排表是按照分词进行存储的,比如我们使用term查 三国志,查询的时候他是不分词的, 但是倒排表中是分词的(三国志,三国,三,国...)
前缀搜索
它会对分词后的 term 进行前缀搜索·它不会分析要搜索字符串,传入的前缀就是想要查找的前缀。默认状态下,前缀查询不做相关度分数计算,它只是将所有匹配的文档返回,然后賦予所有相关分数值为 1 。它的行为更亻象是一个过滤器而不是查洵。两者实际的区别就是过滤器是可以被缓存的,而前缀查询不行。prefix的原理:需要遍历所有倒排索引,并比较每个 term 是否已所指定的前缀开头。
注意.这里的前缀是指分词后的一个前缀而不是我们存入的数据的前缀
例如:
json
{
"query": {
"prefix": {
"title": "三"
}
}
}
结果:
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 21,
"successful": 21,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "book",
"_type": "_doc",
"_id": "5",
"_score": 1.0,
"_source": {
"id": 5,
"title": "三国志2",
"price": 100
}
},
{
"_index": "book",
"_type": "_doc",
"_id": "10",
"_score": 1.0,
"_source": {
"id": 10,
"title": "陈寿写的三国志",
"price": 199
}
},
{
"_index": "book",
"_type": "_doc",
"_id": "18",
"_score": 1.0,
"_source": {
"id": 18,
"title": "三个国家的孩子拥有不同的状志",
"price": 199
}
},
{
"_index": "book",
"_type": "_doc",
"_id": "20",
"_score": 1.0,
"_source": {
"id": 20,
"title": "三",
"price": 199
}
}
]
}
}
json
{
"query": {
"prefix": {
"title": "国"
}
}
}