【ElasticSearch实用篇-02】基本增删改查
ElasticSearch实用篇整体栏目
内容 | 链接地址 |
---|---|
【一】ElasticSearch实用篇-需求分析和数据制造 | https://zhenghuisheng.blog.csdn.net/article/details/149178534 |
【二】ElasticSearch实用篇-基本增删改查 | https://zhenghuisheng.blog.csdn.net/article/details/149202330 |
ElasticSearch实用篇
- 1,mapping映射
- 2,关键字简单查询(CRUD)
- 2.1,term精确查询
- 2.2,match匹配
- 2.3,range范围查询
- 2.4,exists过滤
- 3,关键字部分匹配查询
- 3.1,prefix前缀查询
- 3.2,wildcard通配符查询
- 3.3,fuzzy迷糊查询
如需转载,请附上链接:https://blog.csdn.net/zhenghuishengq/article/details/149202330
上一篇完成了数据的生成,接下来这篇查看一下官网,看一下官方文档,对一些开发中常用的重点知识熟悉一下。其官方地址如下:https://www.elastic.co/guide/cn/elasticsearch/guide/current/data-in-data-out.html
实际开发都是通过canal将数据库中的数据同步到es中,因此此系列也不讲数据的增删改,主要是讲解数据的查询,elasticsearch顾名思义,就是为了查询而生,如果还需要涉及到大量的增删改,那么此非关系型数据库也得好好考虑下是否合适当前业务。
1,mapping映射
获取某一个索引的mapping得到命令如下,在kibana上面就可以详细的展示出来
GET /user/_mapping
在es的官方文档中,支持的基本数据映射类型如下,分别是字符串类型,整型,浮点数据类型,布尔数据类型和date日期类型。基础数据类型适合精确匹配或者范围匹配,同时也支持排序等
- 字符串: string
- 整数 :byte, short, integer, long
- 浮点数: float, double。一般优先推荐使用double,适用于精度要求高的金额、评分
- 布尔型:boolean
- 日期:date。支持多种格式,比如说 "yyyy-MM-dd HH:mm:ss"和"yyyy-MM-dd"等
除了基本数据类型之外,还有es核心的两个数据类型,text数据类型和keyword数据类型,text数据类型会根据对应的分词器进行分词,适合通过match关键字进行匹配,keyword关键字不进行分词,得通过term进行精确匹配
- text:全文匹配数据类型,可以分词,不支持排序,适合match匹配查询,如用户评论、标题、文章内容
- keyword:精确匹配数据类型,不能分词,支持排序,适合term匹配查询,如性别、城市、手机号、状态码
当然如果text字段也想支持聚合或者排序,那么也可以使用以下这种方式
"title": {"type": "text","fields": {"keyword": { "type": "keyword" }}
}
再次看一下上文index索引手动制造数据的mapping,其内容如下
{"user" : {"mappings" : {"properties" : {"_class" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"birthDay" : {"type" : "long"},"birthMonth" : {"type" : "long"},"birthYear" : {"type" : "long"},"delFlag" : {"type" : "long"},"eduLevel" : {"type" : "long"},"height" : {"type" : "long"},"id" : {"type" : "long"},"liveCity" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"liveProvince" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"nickName" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"regCity" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"regProvince" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"sex" : {"type" : "long"},"weight" : {"type" : "long"}}}}
}
2,关键字简单查询(CRUD)
以官方文档的查询为主:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_finding_exact_values.html
2.1,term精确查询
比如通过id查询,可以直接通过以下命令进行查询,id是一个基本数据类型
GET /user/_search
{"query": {"term": {"id":"1937084380169277574"}}
}
如果是一个text文本数据类型,比如设置的liveCity生活城市,那么可以在字段强制加一个keyword
GET /user/_search
{"query": {"term": {"liveCity.keyword": "贵阳市"}}
}
如果完全不想打分,不想有任何加权,可以直接使用filter表达式,这样默认分值是1.0,这个是默认值,内部并没有参与打分
GET /user/_search
{"query": {"constant_score": {"filter": {"term": {"liveCity.keyword": "贵阳市"}}}}
}
对应的java代码如下,后续的代码统一在 buildBoolQueryBuilder 这个方法里改构造条件
@Service
@Slf4j
public class UserTermSearchServiceImpl implements UserTermSearchService {@Resourceprivate RestHighLevelClient restHighLevelClient;@Overridepublic AjaxResult matchSearch() {// 构建查询BoolQueryBuilder queryBuilder = buildBoolQueryBuilder();// 设置查询源SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();sourceBuilder.query(queryBuilder);// 这里可以查看详细的请求 直接拿到kibana上执行log.info("构建条件为:,{}", sourceBuilder);sourceBuilder.size(10);// 构建查询请求SearchRequest searchRequest = new SearchRequest("user");searchRequest.source(sourceBuilder);List<UserEO> userMatchList = new ArrayList<>();try {SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);SearchHit[] hits = response.getHits().getHits();log.info("匹配的返回结果为,{}", hits.length);// 强转eo返回userMatchList = convertToUserEoList(hits);}catch (Exception e){log.error("matchSearch error:", e);}return AjaxResult.success(userMatchList);}/*** 构造查询条件* @return*/private BoolQueryBuilder buildBoolQueryBuilder(){BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 精确查询// 添加查询条件// boolQueryBuilder.filter(QueryBuilders.termQuery("id", "拉萨市"));boolQueryBuilder.filter(QueryBuilders.termQuery("liveCity.keyword", "拉萨市"));return boolQueryBuilder;}public List<UserEO> convertToUserEoList(SearchHit[] hits) {if (hits.length == 0){return new ArrayList<>();}JSONArray jsonArray = new JSONArray();for (SearchHit hit : hits) {JSONObject jsonObject = JSONUtil.parseObj(hit.getSourceAsMap());jsonArray.add(jsonObject);}return JSONUtil.toList(jsonArray, UserEO.class);}
}
除了单值查询之外,terms也可以实现多值的精确匹配查询
GET /user/_search
{"query": {"terms": {"liveCity.keyword": ["贵阳市","北京市"]}}
}
对应的代码如下,通过termsQuery 关键字实现
boolQueryBuilder.filter(QueryBuilders.termsQuery("liveCity.keyword", Arrays.asList("贵阳市", "北京市")));
2.2,match匹配
match用于全文匹配,如果是text数据类型,那么会直接分词,比如即使没有完整的西藏数据,也会根据这个分词找到这些数据
GET /user/_search
{"query": {"match": {"liveProvince": "西藏"}}
}
如果是其他基础数据类型或者keyword,那么只能全文精确匹配,如果分析就会找不到,比如以下分词之后就会找不到
GET /user/_search
{"query": {"match": {"liveProvince.keyword": "西藏"}}
}
对应的代码如下,因为match一般设计的是全文分词检索,所以外部用must而不是filter
boolQueryBuilder.must(QueryBuilders.matchQuery("liveProvince", "西藏"));
除了单字段匹配之外,也可以通过multi_match实现多字段匹配,比如查老家城市和居住城市都带有北京的
GET /user/_search
{"query": {"multi_match": {"query": "北京市","fields": ["liveCity", "regCity"]}}
}
其对应的代码如下
boolQueryBuilder.must(QueryBuilders.multiMatchQuery("北京市", new String[]{"liveCity","regCity"}));
2.3,range范围查询
这个用于范围1查询,比如查身高,体重,年龄等,如下查询身高170-175的,
GET /user/_search
{"query": {"range": {"height": {"gte": 170,"lte": 175}}}
}
同时满足三个的,查询身高170-175的,1998-2001年出生的,体重在120-140的,也可以直接使用filter组合的方式
GET /user/_search
{"query": {"bool": {"filter": [{"range": {"height": {"gte": 170,"lte": 175}}},{"range": {"birthYear": {"gte": 1998,"lte": 2001}}},{"range": {"weight": {"gte": 120,"lte": 140}}}]}}
}
对应的代码如下,基本数据类型直接选择filter就行,效果和must一样,filter因为有缓存,filter效率会更快
// 身高170-175
boolQueryBuilder.filter(QueryBuilders.rangeQuery("height").gte(170).lte(175));
// 1998-2001年出生的
boolQueryBuilder.filter(QueryBuilders.rangeQuery("birthYear").gte(1998).lte(2001));
// 体重在120-140
boolQueryBuilder.filter(QueryBuilders.rangeQuery("weight").gte(120).lte(140));
2.4,exists过滤
exist类似于mysql的is not null,就是文档包含这个字段的所有值,当然也包含空字符串""
GET /user/_search
{"query": {"exists": {"field": "nickName"}}
}
如果想过滤这种"",那么写法如下
GET /user/_search
{"query": {"bool": {"must": [{"exists": {"field": "nickName"}}],"must_not": [{"term": {"nickName.keyword": ""}}]}}
}
对应的代码如下
boolQueryBuilder.must(QueryBuilders.existsQuery("nickName"));
boolQueryBuilder.mustNot(QueryBuilders.termQuery("nickName.keyword", ""));
3,关键字部分匹配查询
2的那几个关键字使用的频率相对较高,接下来是用的频率没那么高的关键字查询,比如前缀查询,模糊查询和通配符查询等
3.1,prefix前缀查询
用官网的话说在这个前缀查询更像是一个过滤器而不是查询,将所有匹配的文档都返回,然后赋值评分为1。比如下面这个例子,我查询居住省份带有江字的数据
GET /user/_search
{"query": {"prefix": {"liveProvince":"江"}}
}
如果查询的字符串短,且es中的数据量大,那么使用这种方式效率可能会比较低一些,mysql对应的语句如下
select * from user where liveProvince like '%江'
3.2,wildcard通配符查询
通配符字段一般适用于keyword字段类型,不太适用于text类型,因为text类型会分词。改匹配一般也是用于模糊匹配,也是需要全文及检索,效率较差。它使用标准的 shell 通配符查询: ?
匹配任意字符, *
匹配 0 或多个字符
?任意字符匹配
?相当于一个占位符,允许让任意一个值去替换它,比如下面的查询就会将江西省和江苏省的一起查询出来
GET /user/_search
{"query": {"wildcard": {"liveProvince.keyword": "江?省"}}
}
比如a?c,那么可以匹配的就是abc,adc;不能匹配的就是ac,abdc
** *匹配字符 **
如我要查询居住城市中带有京字的,下面这个查询就是会将北京市,南京市给查询出来
GET /user/_search
{"query": {"wildcard": {"liveCity.keyword": "*京*"}}
}
*匹配符就是对应mysql的like, *在哪边%就加在哪边
*京*: like '%京%'
*京: like '%京'
京*: like '京%'
3.3,fuzzy迷糊查询
查询用于在拼写可能有错误或不完全匹配的情况下,找到“最接近”的字符串。fuzzy只能用于text类型,keyword字段不会分词,所以不推荐使用。举例子如下,因为ik分词器的原因,所以都用英文代替下
字符串 A | 字符串 B | 编辑距离 |
---|---|---|
cat | cut | 1 |
cat | cats | 1 |
cat | act | 2 |
cat | dog | 3 |
如查询江西,那么能查出来的就有陕西省,山西省,江苏省,西藏自治区,广西壮族自治区
GET /user/_search
{"query": {"fuzzy": {"liveProvince": {"value": "江西","fuzziness": 1}}}
}
用一句话来形容fuzzy就是智能但是低效