概要
基于上一篇 Elasticsearch 录入测试数据后,抱着先能用再说的想法,选择率先学习查询,本文用于整理查询相关语法,用于备忘,也加深记忆
环境
数据准备
如果已经看过上一篇文章《ELK学习笔记(一)——服务部署与Elasticsearch基础知识》,那么补充一条曹天元的《上帝掷骰子吗》即可,如果没添加数据,可将如下命令保存到 load_data.sh 脚本执行录入数据
curl -XPOST http://127.0.0.1:9200/books_idx/_doc/9787111606420?pretty -H 'Content-Type: application/json' -d '{"name": "深入浅出Rust", "author": "范长春", "publish": "机械工业出版社", "isbn": "9787111606420", "pubdate": "2018"}'
curl -XPOST http://127.0.0.1:9200/books_idx/_doc/9787506365437?pretty -H 'Content-Type: application/json' -d '{"name": "活着", "author": "余华", "publish": "作家出版社", "isbn": "9787506365437", "pubdate": "2017"}'
curl -XPOST http://127.0.0.1:9200/books_idx/_doc/9787115373557?pretty -H 'Content-Type: application/json' -d '{"name": "数学之美", "author": "吴军", "publish": "人民邮电出版社", "isbn": "9787115373557", "pubdate": "2014"}'
curl -XPOST http://127.0.0.1:9200/books_idx/_doc/9787121202087?pretty -H 'Content-Type: application/json' -d '{"name": "LaTeX入门", "author": "刘海洋", "publish": "电子工业出版社", "isbn": "9787121202087", "pubdate": "2013"}'
curl -XPOST http://127.0.0.1:9200/books_idx/_doc/9787121354854?pretty -H 'Content-Type: application/json' -d '{"name": "Rust编程之道", "author": "张汉东", "publish": "电子工业出版社", "isbn": "9787121354854", "pubdate": "2019"}'
curl -XPOST http://127.0.0.1:9200/books_idx/_doc/9787550218895?pretty -H 'Content-Type: application/json' -d '{"name": "上帝掷骰子吗", "author": "曹天元", "publish": "北京联合出版公司", "isbn": "9787550218895", "pubdate": "2013"}'
知识点
- 在上一篇博文中记录过一些查询语句,通过URL参数传递的,另一种更好的方式是通过Body进行传递,功能更丰富且易于编辑
- Elasticsearch 接口可以使用 Post 查询数据,尽管语义上 Get 更加符合,详见 <一个带请求体的 GET 请求?>
一个带请求体的 GET 请求?
某些特定语言(特别是 JavaScript)的 HTTP 库是不允许
GET
请求带有请求体的。事实上,一些使用者对于GET
请求可以带请求体感到非常的吃惊。而事实是这个RFC文档 RFC 7231— 一个专门负责处理 HTTP 语义和内容的文档 — 并没有规定一个带有请求体的
GET
请求应该如何处理!结果是,一些 HTTP 服务器允许这样子,而有一些 — 特别是一些用于缓存和代理的服务器 — 则不允许。对于一个查询请求,Elasticsearch 的工程师偏向于使用
GET
方式,因为他们觉得它比POST
能更好的描述信息检索(retrieving information)的行为。然而,因为带请求体的GET
请求并不被广泛支持,所以search
API同时支持POST
请求
在指定字段内搜索
查询 name
字段内包含 “Rust” 字符串的记录
curl -X GET "localhost:9200/books_idx/_doc/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query" : {
"match" : {
"name" : "Rust"
}
}
}
'
短语搜索
让我们调整一下关键词,之前用 “Rust” 修改为 “Rust编程”
curl -X GET "localhost:9200/books_idx/_doc/_search" -H 'Content-Type: application/json' -d'
{
"query" : {
"match" : {
"name" : "Rust编程"
}
}
}
'
查询结果
搜索 “Rust” 一样,有两条记录,包括《深入浅出Rust》这条记录也会搜索出来,查看结果中有一个 _score
字段,表示这条记录与搜索关键词的相关性,《Rust编程之道》的分值为 3.2306948,深入浅出Rust分值为 0.77530915
PS: 为了节省空间,查询结果的 pretty 参数去掉了
{"took":0,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":2,"relation":"eq"},"max_score":3.7996306,"hits":[{"_index":"books_idx","_type":"_doc","_id":"9787121354854","_score":3.7996306,"_source":{"name":"Rust编程之道","author":"张汉东","publish":"电子工业出版社","isbn":"9787121354854","pubdate":"2019"}},{"_index":"books_idx","_type":"_doc","_id":"9787111606420","_score":0.9517491,"_source":{"name":"深入浅出Rust","author":"范长春","publish":"机械工业出版社","isbn":"9787111606420","pubdate":"2018"}}]}}
这里就需要提及一下 Elasticsearch 与传统数据库的区别了
传统数据库搜索记录,结果是二项的,有或者没有,Elasticsearch 则更关心数据与查询字符串的相关性,数值 1 代表中立,数值越大则相关性越高,这在全文监所,搜索建议这样的场景下,是非常方便的,而传统数据库则很难实现类似的需求
精确匹配搜索
如果我想要精确的搜索包含 “Rust编程” 语句的记录呢,使用 match_phrase 就可以了,结果就只会包含《Rust编程之道》
curl -X GET "localhost:9200/books_idx/_doc/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query" : {
"match_phrase" : {
"name" : "Rust编程"
}
}
}
'
获取多条记录
curl -X GET "localhost:9200/books_idx/_mget?pretty" -H 'Content-Type: application/json' -d'
{
"ids": ["9787111606420", "9787121354854"]
}
'
如果指定文档的 id 值不存在,则返回值的 found
字段值为 false
分页
可以说这个功能非常的有用
URL 参数方式
curl -X GET "localhost:9200/books_idx/_search?size=5&pretty"
curl -X GET "localhost:9200/books_idx/_search?size=5&from=5&pretty"
curl -X GET "localhost:9200/books_idx/_search?size=5&from=10&pretty"
Body 方式
curl -X POST "localhost:9200/books_idx/_search?pretty" -H 'Content-Type: application/json' -d'
{
"from": 0,
"size": 5
}
'
应该限制分页的深度
在分布式系统中深度分页
理解为什么深度分页是有问题的,我们可以假设在一个有 5 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。
现在假设我们请求第 1000 页—结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。
可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。这就是 web 搜索引擎对任何查询都不要返回超过 1000 个结果的原因。
数据过滤
查询发行年份大于等于 2019 年的书籍
curl -X GET "localhost:9200/books_idx/_doc/_search?pretty " -H 'Content-Type: application/json' -d'
{
"query" : {
"bool": {
"must": {
"match" : {
"name" : "Rust"
}
},
"filter": {
"range" : {
"pubdate" : { "gte" : 2019 }
}
}
}
}
}
'
bool
是一个复合语句(Compound),可以包含叶子子句(Leaf clauses),上边搜索命令表示必须包含 Rust 字符串,filter
语句搭配 range
语句,以及 gte
可以检索 pubdate 字段大于等于 2019 的记录
用于比较的可选参数
关键字 | 描述 |
---|---|
gt | Greater than. (大于) |
gte | Greater than or equal to. (大于等于) |
lt | Less than. (小于) |
lte | Less than or equal to. (小于等于) |
format | Date format used to convert date values in the query. (日期格式化) |
更多可选参数参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html
高亮搜索
网页上很多模糊搜索,在结果页上会高亮显示搜索关键字,方便用户判断这条记录和搜索内容相关性
curl -X GET "localhost:9200/books_idx/_doc/_search" -H 'Content-Type: application/json' -d'
{
"query" : {
"match_phrase" : {
"name" : "Rust编程"
}
},
"highlight": {
"fields" : {
"name" : {}
}
}
}
'
搜索结果
{"took":2,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":3.7996309,"hits":[{"_index":"books_idx","_type":"_doc","_id":"9787121354854","_score":3.7996309,"_source":{"name":"Rust编程之道","author":"张汉东","publish":"电子工业出版社","isbn":"9787121354854","pubdate":"2019"},"highlight":{"name":["<em>Rust</em><em>编</em><em>程</em>之道"]}}]}}
聚合分析
使用 eggs
可以进行聚合分析统计,如下查询了数据库中每年出版书籍的数量
curl -X GET "localhost:9200/books_idx/_doc/_search?pretty" -H 'Content-Type: application/json' -d'
{
"aggs": {
"years_count": {
"terms": { "field": "pubdate" }
}
}
}
'
搜索结果(部分 / 数据部分已隐藏)
"aggregations" : {
"years_count" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "2013",
"doc_count" : 2
},
{
"key" : "2014",
"doc_count" : 1
},
{
"key" : "2017",
"doc_count" : 1
},
{
"key" : "2018",
"doc_count" : 1
},
{
"key" : "2019",
"doc_count" : 1
}
]
}
}
可以说非常的方便,不过这里有个自己埋的问题,我没有自己创建映射,映射是在提交数据时自己生成的,根据年份聚合的时候hi报错
Fielddata is disabled on text fields by default. Set fielddata=true on [publish] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead.
我按照提示修改索引参数属性
curl -X PUT "localhost:9200/books_idx/_mapping?pretty" -H 'Content-Type: application/json' -d'
{
"properties": {
"pubdate": {
"type": "text",
"fielddata": true
}
}
}
'
这样解决了问题,但是如果想要使用 “出版社” 来聚合统计,那么搜索结果就炸裂了,默认的 type: text
是告诉这个字段用于全文检索,所以它会按照规则对内容进行拆分创建倒排索引等,并且不能用于聚合搜索,解决方法是为这个字段创建其它类型,已有的索引类型是不能修改的
遗留的小问题下篇文章 Fix,那么关于搜索,先到这里
参考