ES 查询用法
通过ES查询表达式(Query DSL),可以实现复杂的查询功能,ES查询表达式主要由JSON格式编写,可以灵活的组合各种查询语句。
提示:这里先介绍基本的语法结构,后续单独讲解具体的查询语法。
1,查询基本语法结构 GET /{索引名}/_search { "from" : 0, // 返回搜索结果的开始位置 "size" : 10, // 分页大小,一次返回多少数据 "_source" :[ ...需要返回的字段数组... ], "query" : { ...query子句... }, "aggs" : { ..aggs子句.. }, "sort" : { ..sort子句.. } }还支持一次搜索多个索引
GET /order1,order2/_search按前缀匹配索引名
GET /order*/_search查询结果格式
{ "took" : 5, // 查询消耗时间,单位毫秒 "timed_out" : false, // 查询是否超时 "_shards" : { // 本次查询参与的ES分片信息,查询中参与分片的总数,以及这些分片成功了多少个失败了多少个 "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { // hits字段包含我们搜索匹配的结果 "total" : { // 匹配到的文档总数 "value" : 1, // 找到1个文档 "relation" : "eq" }, "max_score" : 1.0, // 匹配到的最大分值 "hits" : [ // 这里就是我们具体的搜索结果,是一个JSON文档数组 ] } } 2.query子句query子句主要用来编写类似SQL的Where语句,支持布尔查询(and/or)、IN、全文搜索、模糊匹配、范围查询(大于小于)。
3.aggs子句aggs子句,主要用来编写统计分析语句,类似SQL的group by语句
4.sort子句sort子句,用来设置排序条件,类似SQL的order by语句
5.ES查询分页ES查询的分页主要通过from和size参数设置,类似MYSQL 的limit和offset语句
例子:
GET /order_v2/_search { "from": 0, "size": 20, "query": { "match_all": {} } }查询所有数据,从第0条数据开始,一次返回20条数据。
6. _source_source用于设置查询结果返回什么字段,类似Select语句后面指定字段。
例子:
GET /order_v2/_search { "_source": ["order_no","shop_id"], "query": { "match_all": {} } }仅返回,order_no和shop_id字段。
二,query查询query子句主要用于编写查询条件,类似SQL中的where语句。
1.匹配单个字段通过match实现全文搜索
语法:
GET /{索引名}/_search { "query": { "match": { "{FIELD}": "{TEXT}" } } }说明:
{FIELD} - 就是我们需要匹配的字段名 {TEXT} - 就是我们需要匹配的内容例子:
GET /article/_search { "query": { "match" : { "title" : "ES教程" } } }article索引中,title字段匹配ES教程的所有文档。
如果title字段的数据类型是text类型,搜索关键词会进行分词处理。
2.精确匹配单个字段如果我们想要类似SQL语句中的等值匹配,不需要进行分词处理,例如:订单号、手机号、时间字段,不需要分值处理,只要精确匹配。
通过term实现精确匹配语法:
GET /{索引名}/_search { "query": { "term": { "{FIELD}": "{VALUE}" } } }说明:
{FIELD} - 就是我们需要匹配的字段名 {VALUE} - 就是我们需要匹配的内容,除了TEXT类型字段以外的任意类型。例子:
GET /order_v2/_search { "query": { "term": { "order_no": "202003131209120999" } } }搜索订单号order_no = "202003131209120999"的文档。
类似SQL语句:
select * from order_v2 where order_no = "202003131209120999" 3.通过terms实现SQL的in语句如果我们要实现SQL中的in语句,一个字段包含给定数组中的任意一个值就匹配。
terms语法:
GET /order_v2/_search { "query": { "terms": { "{FIELD}": [ "{VALUE1}", "{VALUE2}" ] } } }说明:
{FIELD} - 就是我们需要匹配的字段名 {VALUE1}, {VALUE2} … {VALUE N} - 就是我们需要匹配的内容,除了TEXT类型字段以外的任意类型。例子:
GET /order_v2/_search { "query": { "terms": { "shop_id": [123,100,300] } } }搜索order_v2索引中,shop_id字段,只要包含[123,100,300]其中一个值,就算匹配。
类似SQL语句:
select * from order_v2 where shop_id in (123,100,300) 4.范围查询通过range实现范围查询,类似SQL语句中的>, >=, <, <=表达式。
range语法:
GET /{索引名}/_search { "query": { "range": { "{FIELD}": { "gte": 10, "lte": 20 } } } }参数说明:
{FIELD} - 字段名 gte范围参数 - 等价于>= lte范围参数 - 等价于 <= 范围参数可以只写一个,例如:仅保留 “gte”: 10, 则代表 FIELD字段 >= 10范围参数如下:
gt - 大于 ( > ) gte - 大于且等于 ( >= ) lt - 小于 ( < ) lte - 小于且等于 ( <= )例子1:
GET /order_v2/_search { "query": { "range": { "shop_id": { "gte": 10, "lte": 200 } } } }查询order_v2索引中,shop_id >= 10 且 shop_id <= 200的文档
类似SQL:
select * from order_v2 where shop_id >= 10 and shop_id <= 200例子2:
GET /order_v2/_search { "query": { "range": { "shop_id": { "gte": 10 } } } }类似SQL:
select * from order_v2 where shop_id >= 10 5.bool组合查询前面的例子都是设置单个字段的查询条件,如果需要编写类似SQL的Where语句,组合多个字段的查询条件,可以使用bool语句。
5.1. bool查询基本语法结构在ES中bool查询就是用来组合布尔查询条件,布尔查询条件,就是类似SQL中的and (且)、or (或)。
在SQL中,我们需要and和or,还有括号来组合查询条件,在ES中使用bool查询可用做到同样的效果。
bool语法结构:
GET /{索引名}/_search { "query": { "bool": { // bool查询 "must": [], // must条件,类似SQL中的and, 代表必须匹配条件 "must_not": [], // must_not条件,跟must相反,必须不匹配条件 "should": [] // should条件,类似SQL中or, 代表匹配其中一个条件 } } }可以任意选择must、must_not和should条件的参数都是一个数组,意味着他们都支持设置多个条件。
提示:前面介绍的单个字段的匹配语句,都可以用在bool查询语句中进行组合。
5.2. must条件类似SQL的and,代表必须匹配的条件。
语法:
GET /{索引名}/_search { "query": { "bool": { "must": [ {匹配条件1}, {匹配条件2}, ...可以有N个匹配条件... ] } } }例子1:
GET /order_v2/_search { "query": { "bool": { "must": [ { "term": { "order_no": "202003131209120999" } }, { "term": { "shop_id": 123 } } ] } } }这里的Must条件,使用了term精确匹配。
等价SQL:
select * from order_v2 where order_no="202003131209120999" and shop_id=123 5.3. must_not条件跟must的作用相反。
语法:
GET /{索引名}/_search { "query": { "bool": { "must_not": [ {匹配条件1}, {匹配条件2}, ...可以有N个匹配条件... ] } } }例子:
GET /order_v2/_search { "query": { "bool": { "must_not": [ { "term": { "shop_id": 1 } }, { "term": { "shop_id": 2 } } ] } } }等价sql:
select * from order_v2 where shop_id != 1 and shop_id != 2 5.3. should条件类似SQL中的 or, 只要匹配其中一个条件即可
语法:
GET /{索引名}/_search { "query": { "bool": { "should": [ {匹配条件1}, {匹配条件2}, …可以有N个匹配条件… ] } } }例子:
GET /order_v2/_search { "query": { "bool": { "should": [ { "match": { "order_no": "202003131209120999" } }, { "match": { "order_no": "22222222222222222" } } ] } } }等价SQL:
select * from order_v2 where order_no="202003131209120999" or order_no="22222222222222222" 5.4. bool综合例子 GET /order_v2/_search { "query": { "bool": { "should": [ { "bool": { "must": [ { "term": { "order_no": "2020031312091209991" } }, { "range": { "shop_id": { "gte": 10, "lte": 200 } } } ] } }, { "terms": { "tag": [ 1, 2, 3, 4, 5, 12 ] } } ] } } }等价SQL:
select * from order_v2 where (order_no='202003131209120999' and (shop_id>=10 and shop_id<=200)) or tag in (1,2,3,4,5) 三,全文搜索全文搜索是ES的关键特性之一,平时我们使用SQL的like语句,搜索一些文本、字符串是否包含指定的关键词,但是如果两篇文章,都包含我们的关键词,具体那篇文章内容的相关度更高? 这个SQL的like语句是做不到的,更别说like语句的性能问题了。
ES通过分词处理、相关度计算可以解决这个问题,ES内置了一些相关度算法,例如:TF/IDF算法,大体上思想就是,如果一个关键词在一篇文章出现的频率高,并且在其他文章中出现的少,那说明这个关键词与这篇文章的相关度很高。
分词的目的:
主要就是为了提取搜索关键词,理解搜索的意图,我们平时在百度搜索内容的时候,输入的内容可能很长,但不是每个字都对搜索有帮助,所以通过分词算法,我们输入的搜索关键词,会进一步分解成多个关键词,例如:搜索输入 “上海陆家嘴在哪里?”,分词算法可能分解出:“上海、陆家嘴、哪里”,具体会分解出什么关键词,跟具体的分词算法有关。
1.默认全文搜索默认情况下,使用全文搜索很简单,只要将字段类型定义为text类型,然后用match语句匹配即可。
ES对于text类型的字段,在插入数据的时候,会进行分词处理,然后根据分词的结果索引文档,当我们搜索text类型字段的时候,也会先对搜索关键词进行分词处理、然后根据分词的结果去搜索。
ES默认的分词器是standard,对英文比较友好,例如:hello world 会被分解成 hello和world两个关键词,如果是中文会分解成一个一个字,例如:上海大学 会分解成: 上、海、大、学。
在ES中,我们可以通过下面方式测试分词效果:
语法:
GET /_analyze { "text": "需要分词的内容", "analyzer": "分词器" }例如:
GET /_analyze { "text": "hello wolrd", "analyzer": "standard" }使用standard分词器,对hello world进行分词,下面是输出结果:
{ "tokens" : [ { "token" : "hello", "start_offset" : 0, "end_offset" : 5, "type" : "<ALPHANUM>", "position" : 0 }, { "token" : "wolrd", "start_offset" : 6, "end_offset" : 11, "type" : "<ALPHANUM>", "position" : 1 } ] }token就是分解出来的关键词。
下面是对中文分词的结果:
GET /_analyze { "text": "上海大学", "analyzer": "standard" }输出
{ "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 }, { "token" : "学", "start_offset" : 3, "end_offset" : 4, "type" : "<IDEOGRAPHIC>", "position" : 3 } ] }明显被切割成一个个的字了。
中文关键词被分解成一个个的字的主要问题就是搜索结果可能不太准确。
例如:
搜索:上海大学
分词结果:上、海、大、学
下面的内容都会被搜到:
上海大学 海上有条船 上海有好吃的 这条船又大又好看基本上包含这四个字的内容都会被搜到,区别就是相关度的问题,这里除了第一条是相关的,后面的内容基本上跟搜索目的没什么关系。
2.中文分词器ES默认的analyzer(分词器),对英文单词比较友好,对中文分词效果不好。不过ES支持安装分词插件,增加新的分词器。
2.0. 如何指定analyzer默认的分词器不满足需要,可以在定义索引映射的时候,指定text字段的分词器 (analyzer)。
例子:
PUT /article { "mappings": { "properties": { "title": { "type": "text", "analyzer": "smartcn" } } } }只要在定义text字段的时候,增加一个analyzer配置,指定分词器即可,这里指定的分词器是smartcn,后面会介绍怎么安装smartcn插件。
目前中文分词器比较常用的有:smartcn和ik两种, 下面分别介绍这两种分词器。
2.1. smartcn分词器smartcn是目前ES官方推荐的中文分词插件,不过目前不支持自定义词库。
插件安装方式:
{ES安装目录}/bin/elasticsearch-plugin install analysis-smartcn安装完成后,重启ES即可。
smartcn的分词器名字就叫做:smartcn
2.2. smartcn中文分词效果测试分词效果:
GET /_analyze { "text": "红烧牛肉面", "analyzer": "smartcn" }输出:
{ "tokens" : [ { "token" : "红烧", "start_offset" : 0, "end_offset" : 2, "type" : "word", "position" : 0 }, { "token" : "牛肉面", "start_offset" : 2, "end_offset" : 5, "type" : "word", "position" : 1 } ] }红烧牛肉面,被切割为: 红烧、牛肉面 两个词。
2.3. ik分词器ik支持自定义扩展词库,有时候分词的结果不满足我们业务需要,需要根据业务设置专门的词库,词库的作用就是自定义一批关键词,分词的时候优先根据词库设置的关键词分割内容,例如:词库中包含 “上海大学” 关键词,如果对“上海大学在哪里?”进行分词,“上海大学” 会作为一个整体被切割出来。
安装ik插件:
// 到这里找跟自己ES版本一致的插件地址 https://github.com/medcl/elasticsearch-analysis-ik/releases我本地使用的ES版本是7.5.1,所以选择的Ik插件版本地址是:
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip当然你也可以尝试根据我给出这个地址,直接修改版本号,试试看行不行。
安装命令
{ES安装目录}/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip 2.4. ik中文分词效果ik分词插件支持 ik_smart 和 ik_max_word 两种分词器
ik_smart - 粗粒度的分词 ik_max_word - 会尽可能的枚举可能的关键词,就是分词比较细致一些,会分解出更多的关键词例1:
GET /_analyze { "text": "上海人民广场麻辣烫", "analyzer": "ik_max_word" }输出:
{ "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" : 2, "end_offset" : 4, "type" : "CN_WORD", "position" : 2 }, { "token" : "广场", "start_offset" : 4, "end_offset" : 6, "type" : "CN_WORD", "position" : 3 }, { "token" : "麻辣烫", "start_offset" : 6, "end_offset" : 9, "type" : "CN_WORD", "position" : 4 }, { "token" : "麻辣", "start_offset" : 6, "end_offset" : 8, "type" : "CN_WORD", "position" : 5 }, { "token" : "烫", "start_offset" : 8, "end_offset" : 9, "type" : "CN_CHAR", "position" : 6 } ] }例2:
GET /_analyze { "text": "上海人民广场麻辣烫", "analyzer": "ik_smart" }输出:
{ "tokens" : [ { "token" : "上海", "start_offset" : 0, "end_offset" : 2, "type" : "CN_WORD", "position" : 0 }, { "token" : "人民", "start_offset" : 2, "end_offset" : 4, "type" : "CN_WORD", "position" : 1 }, { "token" : "广场", "start_offset" : 4, "end_offset" : 6, "type" : "CN_WORD", "position" : 2 }, { "token" : "麻辣烫", "start_offset" : 6, "end_offset" : 9, "type" : "CN_WORD", "position" : 3 } ] } 2.5. ik自定义词库自定义扩展词库步骤如下:
一、创建配词库文件,以dic作为扩展名
例如:
词库文件:{ES安装目录}/analysis-ik/config/demo.dic
上海大学 复旦大学 人民广场一行一个词条即可
提示:config目录不存在创建一个即可。
二、创建或者修改配置文件
配置文件路径:{ES安装目录}/analysis-ik/config/IKAnalyzer.cfg.xml
IKAnalyzer.cfg.xml配置文件不存在,就创建一个。
配置文件内容:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 扩展配置</comment> <!--用户可以在这里配置自己的扩展字典 --> <entry key="ext_dict">{ES安装目录}/analysis-ik/config/demo.dic</entry> <!--用户可以在这里配置自己的扩展停止词字典,没有可用删掉配置--> <entry key="ext_stopwords">custom/ext_stopword.dic</entry> <!--用户可以在这里配置远程扩展字典,这个配置需要结合下面配置一起使用,没有可用删掉配置 --> <entry key="remote_ext_dict">location</entry> <!--用户可以在这里配置远程扩展停止词字典,没有可用删掉--> <entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry> </properties>三、重启ES即可
提示:Ik新增扩展词库,支持热更新,不用重启ES,使用remote_ext_dict和remote_ext_stopwords配置远程词库地址即可,词库地址需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,ES靠这两个头识别是否需要更新词库,不了解这两个HTTP头,可以搜一下。
四,排序字句ES的默认排序是根据相关性分数排序,如果我们想根据查询结果中的指定字段排序,需要使用sort Processors处理。
sort语法:
GET /{索引名}/_search { "query": { ...查询条件.... }, "sort": [ { "{Field1}": { // 排序字段1 "order": "desc" // 排序方向,asc或者desc, 升序和降序 } }, { "{Field2}": { // 排序字段2 "order": "desc" // 排序方向,asc或者desc, 升序和降序 } } ....多个排序字段..... ] }sort子句支持多个字段排序,类似SQL的order by。
例子1:
GET /order_v2/_search { "query": { "match_all": {} }, "sort": [ { "order_no": { "order": "desc" } }, { "shop_id": { "order": "asc" } } ] }查询order_v2索引的所有结果,结果根据order_no字段降序,order_no相等的时候,再根据shop_id字段升序排序。
类似SQL:
select * from order_v2 order by order_no desc, shop_id asc例子2:
GET /order_v2/_search { "query": { "match_all": {} }, "sort": [ { "user.id": { // 嵌套json对象,使用 点 连接字段名即可 "order": "desc" } } ] }