您现在的位置是:首页 > 技术文章 > 详情<<文章列表阅读 搜索引擎入门——ElasticSearch的基本查询 搜索引擎 ES ElasticSearch wjyuian 2019-10-29 401 1 ### 快速预览 不管处于什么年龄阶段,我个人在学习新事物的时候总会有一个迫切的诉求,那就是希望新事物能以旧方式呈现。所以,我也希望在搭建好ElasticSearch之后,它能像其它数据存储引擎一样,让我快速的进行数据的预览、查询、新增。不知道是不是我的思想过于陈旧了,总觉得熟悉的面貌能让我快速进入角色,开始学习。 好在我也找到了一款Chrome插件,让我们对ElasticSearch数据进行预览等操作。  #### Dejavu > The Missing Data Browser and Web UI for Elasticsearch. 它是一款ElasticSearch专用的浏览器插件,支持ES的查询、数据导入、语句分析等功能: + 导入数据的方式支持在线JSON,JSON文件、CSV文件 + 在线进行数据查询、过滤、数据统计 + 进行数据的CRUD操作  上图就是插件打开之后的主页面,需要输入ES的地址,以及我们需要预览的index的名称,这里是`position`,截图中的数据都是在我通过json文件导入之后的。 当然,我们可以使用官方推荐的`Kibana`。 #### Import Data  点击`Upload File`按钮边上的问号,可以看下格式说明。简单来说,Json格式就是一个Java对象的Json字符串。准备好文件直接上传就行了,不过建议文件大小不要过大,几十兆就行了,否则浏览器容易卡死。 其它功能,我也没做过多的使用,这里就不展开介绍了,大伙自己个儿去试试看吧。 ### 查询Search ElasticSearch提供的`RESTful`风格的接口,通用查询路径是`${host:port}/名称/_search`,查询参数都是以`json`格式提交,所以要求我们将查询语句设置在`request`的`body`中,所以同时要设置`Headers`的`Content-Type`为`application/json`(默认的应该是`text/plain`)。 后续的测试用例,都是通过`Postman`来发送请求,请求的索引名称是之前导入数据的`position`索引。 #### match_all 查询路径是`http://localhost:9200/position/_search`; 提交参数: ```JSON { "query" : { "match_all" : { } }, "sort" : [ { "annualSalary" : "desc"} ], "from" : 2, "size" : 30, "_source" : ["title", "id", "cityId", "location", "annualSalary", "companyId", "*time"] } ``` + `query`:查询语句 + `sort`:排序字段,多个字段按照先后顺序生效 + `from`:类似`mySQL`的`limit`的`first` + `size`:类似`mySQL`的`limit`的`max`,即本次返回条数 + `_source`:返回字段,默认返回文档的所有字段 返回结果: ```JSON { "took": 2, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 377, "relation": "eq" }, "max_score": null, "hits": [ { "_index": "position", "_type": "_doc", "_id": "v1Z_zGwB4dN2kRyBPbIL", "_score": null, "_source": { "annualSalary": "1000000", "edit_time": "26/8/2019 09:58:45", "investigation_time": "26/8/2019 09:36:43", "modify_time": "26/8/2019 07:00:22", "cityId": "30201", "verify_time": "22/8/2019 10:22:53", "title": "商务战略总监", "guarantee_time": "6", "companyId": "71799", "publish_time": "22/8/2019 10:22:53", "keep_contact_time": "工作日", "location": "上海 上海 上海市徐汇区宜山路711号华鑫商务中心2号楼游族大厦", "id": "127029", "status_time": "26/8/2019 09:36:43" }, "sort": [ 1000000 ] } ] } } ``` 简单介绍下返回结果的字段,相信大家自己也能看得懂: + `took`:本次请求所消耗的时间,单位:毫秒 + `time_out`:请求是否超时 + `_shards`:主要针对集群模式;本次搜索涉及了几个分片,以及分片的状态统计,比如成功、失败、跳过 + `hits.max_score`:与查询条件相关度最大的文档的得分;上面的例子中通过查询参数`sort`进行排序配置,所以这一项值是null + `hits.total.value`:总结果数 + `hits.hits.sort`:文档参与排序的排序值,查询参数设置了`sort`值,才会有这一项 + `hits.hits._score`:文档对应的排序值;`match_all`时也会有值,单此时不具备参考意义 #### match/match_phrase 上面提到的`match_all`类似于`Solr`中的`q=*:*`,就是匹配所有文档,没什么具体意义。如果需要匹配具体字段,那么就需要用到`match`了,例如: ```JSON { "query" : { "match" : { "title":"java 上海"} } } ``` 上面的查询条件将会返回`title`中包含`java`或者`上海`的所有文档,也就是说`match`条件下默认是分词结果``或``的匹配逻辑,所以中间有没有空格并不影响。 如果我们需要的是`且`而不是`或`的匹配,那么将`match`换为`match_phrase`即可: ```JSON { "query" : { "match_phrase" : { "title":"T56 java"} } } ``` 只是,这里必须是完全匹配,中间有没有空格,空格前后关键词的顺序都是有影响的。其实就是将`T56 java`作为一个完整的词进行匹配(phrase就是短语的意思),不过大小写不敏感。 #### bool/filter 很简单,这是用来支持更复杂的查询逻辑的,类似`mySQL`的`AND`、`OR`的组合体: ```JSON { "query" : { "bool" : { "must" : [ { "match" : { "title" : "专场" } }, { "match" : { "title" : "工程师" } } ], "must_not" : [ { "match" : { "title" : "北京" } } ], "filter" : { "range" : { "annualSalary" : { "gte" : 400000, "lte" : 600000 } } } } } } ``` 同样的,`match`中的关键字是会进行分词,而且包含单字的词。比如`专场`关键词,将会搜出所有包含`专场`、`专`、`场`三个词中至少一个词的文档。 `must`、`should`的查询语句会影响文档的得分。但是`must_hot`与filter类似,只负责过滤(yes or no),不会对结果的得分产生影响。 ### Query DSL(Domain Specific Language) ElasticSearch定义了一套完整的基于json格式的查询语句语法,json格式的查询语句最终会生成一棵对应的查询树`AST (Abstract Syntax Tree) `,树上有两种节点: + 叶子节点:实际上指定了那个字段应该是哪个值,比如前面提到的`match`、`range`,还有基于分词的`term`等。 + 非叶子节点:包含若干个叶子节点、非叶子节点,也就是一个复合查询。比如上面提到的`bool`就是一个很典型的复合查询,还有后面将会介绍的`dis_max`。 #### 相关性得分 文档的排序是根据其得分值来的,每个文档返回时都会包含一个元数据字段`_score`。所以根据能否对文档的得分产生影响,将查询文本分为两类:`Query`和`Filter`,即查询和过滤,上面提到过的`must`、`should`属于`Query context`,而`must_hot`与`filter`则属于`Filter context`。 比如下面这段查询语句: ```JSON { "query" : { "bool" : { "must" : [ { "match" : { "title" : "java" } }, { "match" : { "title" : "工程师" } } ], "must_not" : [ { "match" : { "title" : "北京" } } ], "filter" : [ { "term" : { "cityId" : "30201" } } ] } }, "from" : 2, "size" : 30, "_source" : ["title", "id", "cityId", "location", "annualSalary", "companyId", "*time"] } ``` **注意** 1. 无论是`Elasticsearch`还是`Solr`,其得分值都是单精度的浮点型;因此所有实际的得分值精度超过其范围的,都将会被转为float而丢失精度。 2. 我们只将那些希望对排序产生影响的查询语句放置在`Query context`中,而将其它都放置在`Filter context`中。这样能提高查询性能,并充分利用缓存。 ### 复合查询(非叶子节点) 既然是树形查询语句,那么非叶子节点下当然可以是叶子节点,或者其他非叶子节点了。所以复合查询就是基础查询或其他复合查询的组合体,它会将其包含的子查询的结果进行合并、得分进行综合,亦或是仅仅将`Query context`转为`Filter context`。 参考文档[《Compound queries》](https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html)。 #### bool查询 它可以将基础查询或者其它复合查询进行组合,包含`must`、`should`、`must_not`和`filter`。其中,`must`、`should`属于`Query context`,会影响文档得分;而`must_hot`与`filter`则属于`Filter context`,不会影响文档得分。 #### boosting查询 ```JSON { "query" : { "boosting" : { "positive" : { "match" : { "title" : "java" } }, "negative" : { "match_phrase" : { "title" : "专家" } }, "negative_boost": 0.2 } }, "from" : 0, "size" : 30, "_source" : ["title"] } ``` + `positive`:必填,所有返回文档都满足这个查询条件 + `negative`:必填,如果返回文档同时满足这个条件,则会重新计算得分: 1. 获取`positive`的初始得分 2. 将初始得分乘以`negative_boost`值作为该文档的最后得分 + `negative_boost`:必填,当返回文档满足`negative`条件时,文档得分会被乘以这个值,这个值必须在0~1之间。 #### constant_score查询 ```JSON { "query" : { "constant_score" : { "filter" : { "match" : { "title" : "java" } }, "boost" : 2 } }, "from" : 0, "size" : 30, "_source" : ["title"] } ``` 所有符合`filter`条件的文档,其得分都是`boost`指定值,默认是1。 #### dis_max查询 ```JSON { "query": { "dis_max" : { "queries" : [ { "term" : { "title" : "java" } }, { "term" : { "cityId" : "30101" } }, { "term" : { "cityId" : "33102" } }, { "term" : { "location" : "北京" } } ], "tie_breaker" : 0.5 } }, "from" : 0, "size" : 30, "_source" : ["title", "cityId", "location"] } ``` + `queries`:必填,是一个数组,数组里面是独立的查询语句,可以是复合的(也就是本节提到的所有复合查询),也可以是基础的。Elasticsearch会返回满足数组中任意一个查询条件的文档,如果有文档同时满足多条语句,则会使用分数最高的值作为最终得分,记为`highestScore`。 + `tie_breaker`:可选,范围是0到1。如果文档满足了多条语句,则除去得分最高的语句,系统会将剩余的匹配语句对应的得分都乘以该值`tie_breaker`。最后将`highestScore`与前面的乘积得分相加,最终值作为`_score`返回。 所以,`tie_breaker`为0时,最终得分就是得分最高的查询语句的得分;`tie_breaker`不为0时,所有匹配语句都会对得分产生正面影响,不过影响最大的还是得分最高的那条语句。 #### function_score查询 这一节比较复杂,将会另开一篇文章进行阐述。 ### 待分析问题 + 多种复合查询之间如何复合 + `match`、`match_phrase`、`term`等各种匹配之间的异同 ---- 相关文章 搜索引擎进阶——IK扩展之动态加载与同义词 应用算法学习(一)—— TopN算法 搜索引擎进阶——solr自定义function 搜索引擎进阶——Lucene链表操作解析 搜索引擎入门——solr筛选器facet的应用 搜索引擎进阶——关键字预处理模块 搜索引擎入门——启动第一个Solr应用 搜索引擎入门——聊聊schema.xml配置 搜索引擎入门——Solr查询参数详解以及如何使用Java完成对接 搜索引擎入门——什么是中文分词以及它对于搜索引擎的意义 栏目导航 关于我 不止技术 工程化应用(23) 技术学习/探索(32) 自娱自乐(2) 还有生活 随便写写(1) 娱乐/放松(1) 点击排行 SpringBoot2从零开始(二)——多数据源配置 搜索引擎进阶——IK扩展之动态加载与同义词 从零开发参数同步框架(二)—— 前期准备之工具类 Nginx的nginx.conf配置部分解释 springMVC中controller参数拦截问题处理 Maven项目一键打包、上传、重启服务器 微信小程序深入踩坑总结 微信小程序的搜索高亮、自定义导航条等踩坑记录 标签云 Java(19) 搜索引擎(13) Solr(7) 参数同步(6) SpringBoot(4) ES(3) ElasticSearch(3) JVM(3) Netty(3) Spring(3) mongoDB(3) 设计模式(3) Curator(2) Docker(2) Dubbo(2) 大家推荐 魔神重返战场!厄祭战争的巴巴托斯:第四形态 搜索引擎入门——Solr查询参数详解以及如何使用Java完成对接 来聊一聊这个被淘汰的图片验证码 搜索引擎入门——聊聊schema.xml配置 搜索引擎入门——启动第一个Solr应用 君子性非异也,善假于物也——功能强大的Postman 择其善而从之——我为什么开始学习ElasticSearch 实现一个关于队列的伪需求是一种怎样的体验