上一篇:ElasticSearch 03 -- 基础使用_fengxianaa的博客-CSDN博客
1. 批量操作
使用 _bulk 命令,是es提供的一种批量增删改的操作API。
bulk对JSON串有着严格的要求:每个JSON串一行
POST _bulk
{"delete":{"_index":"hero","_id":"3"}}
{"create":{"_index":"hero","_id":"4"}}
{"name":"西施","skill":"最有价值之物,给最珍贵之人"}
{"update":{"_index":"person","_id":"2"}}
{"doc":{"skill":"让妲己看看你的心"}}
解释:
- {"delete":{"_index":"hero","_id":"3"}}:删除id是3的数据
- {"create":{"_index":"hero","_id":"4"}}:添加数据,内容是:{"name":"西施","skill":"最有价值之物,给最珍贵之人"}
- {"update":{"_index":"hero","_id":"2"}}:更新数据,内容是:{"doc":{"skill":"让妲己看看你的心"}}
结果:id=3的已经删除掉,成功添加 id=4 的数据,成功修改 id=2 的数据
使用 java API:
private static void bulk() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
//1. 组装bulk请求对象
BulkRequest bulkRequest = new BulkRequest();
//1.1 删除id=4的文档
DeleteRequest delReq = new DeleteRequest("hero","4");
bulkRequest.add(delReq);
//1.2 创建id=5的文档
Map<String,Object> map = new HashMap<>();
map.put("name","甄姬");
map.put("skill","小女子尚在阵前,大丈夫却要回城?");
IndexRequest createDoc = new IndexRequest("hero")//hero 是索引名
.id("5")//指定id
.source(map);//参数也可以螫map
bulkRequest.add(createDoc);
//1.3 更新id=1的文档
Map<String,Object> map2 = new HashMap<>();
map2.put("skill","没有心,就不会受伤");
//更新数据也可以使用UpdateRequest,更新指定字段
UpdateRequest updateRequest = new UpdateRequest("hero","2");
updateRequest.doc(map2);
bulkRequest.add(updateRequest);
BulkResponse responses = client.bulk(bulkRequest, RequestOptions.DEFAULT);
//输出每个请求的执行结果
for(BulkItemResponse item : responses.getItems()){
System.out.println(item.status());
}
client.close();
}
Kibana查询:id=4 的已经删除掉,成功添加 id=5 的数据,成功修改 id=2 的数据
使用场景:批量导入数据库中的数据
private static void importData() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
BulkRequest bulkRequest = new BulkRequest();
for(int i =6;i<100;i++){
Map<String,Object> map = new HashMap<>();
map.put("name","嫦娥_" + i);
map.put("skill","像做梦一样");
IndexRequest createDoc = new IndexRequest("hero")//hero 是索引名
.id(i + "")//指定id
.source(map);//参数也可以螫map
bulkRequest.add(createDoc);
}
BulkResponse responses = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(responses.status());
client.close();
}
2. 查询--重点
1. 查询所有数据
- Kibana上脚本使用
# 标准写法
GET hero/_search
{
"query": {
"match_all": {}
}
}
# 非标准:GET person/_search
但是 ES 默认返回10条数据,当然也可以指定返回的条数
# from:从哪里开始,size:指定返回的条数,可以用这俩参数做分页查询
GET hero/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 3
}
- java API
private static void queryAll() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
//1. 创建请求对象
SearchRequest request = new SearchRequest("hero");// hero:索引名
//1.1 查询条件构造器
SearchSourceBuilder builder = new SearchSourceBuilder();
//1.2 组装查询条件,目前是查询所有
builder.query(QueryBuilders.matchAllQuery());
//1.3 设置分页条件
builder.from(0);
builder.size(5);
//1.4 把查询条件放到requst中
request.source(builder);
//2. 发送查询请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//3. 拿到返回结果
SearchHits hits = response.getHits();
//3.1 输出总条数
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
//3.2 取出数据内容
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
结果:
2. term查询
使用“term”查询,不会对关键字分词,适合查询类型是“keyword”的字段
GET hero/_search
{
"query": {
"term": {
"name": {
"value": "亚瑟"
}
}
}
}
结果:
java API
private static void termQuery() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
// term查询,使用 termQuery 方法
builder.query(QueryBuilders.termQuery("name","亚瑟"));
builder.from(0);
builder.size(5);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
结果:
3. match 查询
先更新一条数据
POST hero/_doc/2
{
"name":"妲己",
"skill":"王者荣耀,没有心,就不会受伤"
}
会对查询的关键字分词,然后用分词后的结果分别查询,最后取并集
# match查询
GET hero/_search
{
"query": {
"match": {
"skill": "王者不可阻挡"
}
}
}
结果:
因为“王者不可阻挡”的分词结果是:王者、不可、阻挡
查询时,根据:王者、不可、阻挡,分别查询,最后再获取并集,所以结果是2条
但是有时,需要最终结果中同时包含:王者、不可、阻挡,这 3 个词
# match查询,取交集
GET hero/_search
{
"query": {
"match": {
"skill": {
"query": "王者不可阻挡",
"operator": "and"
}
}
}
}
分别查询后,取交集,结果:
Java API
private static void matchQuery() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
// match 查询,使用 matchQuery 方法
builder.query(QueryBuilders.matchQuery("skill","王者不可阻挡").operator(Operator.AND));
builder.from(0);
builder.size(5);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
结果:
4. 模糊查询
1. 问题
执行下面命令:
# match查询
GET hero/_search
{
"query": {
"match": {
"skill": {
"query": "王"
}
}
}
}
发现一个结果都没有
这是因为:
- 我们存进去的数据经过分词后,没有“王”这个词
- 目前我们用的是等值查询
所以,没有结果
2. wildcard
对查询的关键字分词,还可以使用通配符进行模糊查询
- ?:代表任意单个字符
- 王?:匹配 ES 的词条中以“王”开头,2个字的词条
- *:代表0个或多个字符
- 王*:匹配 ES 的词条中以“王”开头的词条
# wildcard 模糊查询
GET hero/_search
{
"query": {
"wildcard": {
"skill": {
"value": "王?"
}
}
}
}
java
private static void wildcard() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
// match 查询,使用 matchQuery 方法
builder.query(QueryBuilders.wildcardQuery("skill","王?"));
builder.from(0);
builder.size(5);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
结果:
3. 正则
利用正则表达式去查询,只做了解
一个不错的正则网站:regex101: build, test, and debug regex
# 正则表达式查询
GET hero/_search
{
"query": {
"regexp": {
"skill": ".*荣耀"
}
}
}
结果:
4. 前缀查询
前缀查询也是一般用于“keyword”类型的字段
# 前缀查询
GET hero/_search
{
"query": {
"prefix": {
"name": {
"value": "嫦娥_"
}
}
}
}
5. java API
private static void likeQuery() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
// wildcard 查询,使用 wildcardQuery 方法
// builder.query(QueryBuilders.wildcardQuery("hero","王?"));
// 正则查询,使用 regexpQuery 方法
// builder.query(QueryBuilders.regexpQuery("hero",".*荣耀"));
// 前缀查询,使用 prefixQuery 方法
builder.query(QueryBuilders.prefixQuery("name","嫦娥_"));
builder.from(0);
builder.size(5);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
5. 范围查询
范围查询也是很常见的操作,比如:
目前我们的数据不支持范围查询,所以重新导入一些
private static void importData() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
BulkRequest bulkRequest = new BulkRequest();
for(int i =6;i<100;i++){
Map<String,Object> map = new HashMap<>();
map.put("name","嫦娥_" + i);
map.put("skill","像做梦一样");
map.put("skill_num", i);//增加一个字段
IndexRequest createDoc = new IndexRequest("hero")//hero 是索引名
.id(i + "")//指定id
.source(map);//参数也可以螫map
bulkRequest.add(createDoc);
}
BulkResponse responses = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(responses.status());
client.close();
}
- Kibana脚本
# 范围查询
GET hero/_search
{
"query": {
"range": {
"skill_num": {
"gte": 6,
"lte": 7
}
}
}
}
# 查询 skill_num>=6 && skill_num<=7 的数据
# 查询 skill_num>6的数据,应该这样写:"gt": 6,
- java API
private static void rangeQuery() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
// 范围查询,使用 rangeQuery 方法
builder.query(QueryBuilders.rangeQuery("skill_num").gte(6).lte(7));
builder.from(0);
builder.size(5);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
- 排序
根据范围查询一般都会排个序
# 范围查询
GET hero/_search
{
"query": {
"range": {
"skill_num": {
"gte": 6,
"lte": 7
}
}
},
"sort": [
{
"skill_num": {
"order": "desc"
}
}
]
}
# desc 降序
java API
private static void rangeQuery() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
// 范围查询,使用 rangeQuery 方法
builder.query(QueryBuilders.rangeQuery("skill_num").gte(6).lte(7));
builder.from(0);
builder.size(5);
// 排序
builder.sort("skill_num", SortOrder.DESC);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
6. 多字段查询
上面我们都是查询单个字段,其实也可以同时查询多个字段
1. queryString
- 对查询关键字分词,然后用分词后的结果分别查询,最后取并集
- 可以指定查询多个字段
- 查询多个字段
# 分词后,查询多个字段
GET hero/_search
{
"query": {
"query_string": {
"fields": ["skill","name"],
"query": "亚瑟的荣耀",
"analyzer": "ik_max_word"
}
}
}
- 手动对关键字分词
# 分词后,查询多个字段
GET hero/_search
{
"query": {
"query_string": {
"fields": ["skill","name"],
"query": "亚瑟 AND 荣耀",
"analyzer": "ik_max_word"
}
}
}
# "亚瑟 AND 荣耀":结果中要同时包含 亚瑟 和 荣耀 这两个词条
# "亚瑟 OR 荣耀":结果中要包含 亚瑟 或 荣耀
2. sampleQueryString
它跟 queryString 的区别是:不支持 OR、AND 这样的连接符
# simple_query_string
GET hero/_search
{
"query": {
"simple_query_string": {
"fields": ["skill","name"],
"query": "亚瑟 AND 荣耀",
"analyzer": "ik_max_word"
}
}
}
# "亚瑟 AND 荣耀":会切分成 亚瑟 AND 荣耀 3个词, 然后分别查询,最后取并集
返回两个结果
3. java API
private static void queryString() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
// queryString 查询,使用 queryStringQuery 方法
// builder.query(QueryBuilders.queryStringQuery("亚瑟的荣耀")
// .field("skill").field("name").analyzer("ik_max_word"));
// simpleQueryString 查询,使用 simpleQueryStringQuery 方法
builder.query(QueryBuilders.simpleQueryStringQuery("亚瑟 AND 荣耀").field("skill").field("name").analyzer("ik_max_word"));
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
7. 布尔查询
连接多个查询条件,比如:
关键字:
- must: 条件必须成立
- must_not: 条件必须不成立
- should: 条件可以不成立
- filter: 条件必须成立,但是性能比 must高,因为不计算分数
默认情况下,ES 会给查询到的结果计算分数,得分高的放到前面
- 查询 name="亚瑟",skill 包含 “王者” 的数据
GET hero/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"name": {
"value": "亚瑟"
}
}
},
{
"match": {
"skill":"王者"
}
}
]
}
}
}
- 配合 filter 使用
# 查询 skill 包含 “王者”,name="妲己",的数据
GET hero/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skill": "王者"
}
}
],
"filter": [
{
"term": {
"name": "妲己"
}
}
]
}
}
}
- should 条件可以不成立
# 查询 name=亚瑟 或 老亚瑟 的 数据
GET hero/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"name": {
"value": "亚瑟"
}
}
},
{
"term": {
"name":{
"value": "老亚瑟"
}
}
}
]
}
}
}
- Java API
private static void boolQuery() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// person:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
// bool 查询,使用 boolQuery 方法
builder.query(QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("skill","王者"))
.filter(QueryBuilders.termQuery("name","妲己")));
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
String content = hit.getSourceAsString();//以字符串形式获取数据内容
System.out.println(content);
}
client.close();
}
8. 聚合查询
分为两种:
- 指标聚合:类似数据库中的:max、min、avg、sum等函数
- 分组:类似数据库中的 group by
重新生成数据
DELETE hero
# 创建索引,并添加映射
PUT hero
{
"mappings": {
"properties": {
"id":{
"type": "integer"
},
"name":{
"type":"keyword"
},
"skill":{
"type":"text"
},
"skill_num":{
"type": "integer"
},
"type":{
"type": "keyword"
}
}
}
}
POST _bulk
{"create":{"_index":"hero","_id":"1"}}
{"id":1,"name":"亚瑟","skill":"王者,以圣剑的名义,冲锋","skill_num":3,"type":"战士"}
{"create":{"_index":"hero","_id":"2"}}
{"id":2,"name":"妲己","skill":"王者,没有心,就不会受伤","skill_num":3,"type":"法师"}
{"create":{"_index":"hero","_id":"3"}}
{"id":3,"name":"甄姬","skill":"王者,还人间一片净土","skill_num":3,"type":"法师"}
{"create":{"_index":"hero","_id":"4"}}
{"id":4,"name":"西施","skill":"王者,最有价值之物,给最珍贵之人","skill_num":3,"type":"法师"}
GET hero/_search
- 指标聚合
# 查询 skill 中包含 王者 的数据,并拿到结果中最大的skill_num
GET hero/_search
{
"query": {
"match": {
"skill": "王者"
}
},
"aggs": {
"max_skill_num": {
"max": {
"field": "skill_num"
}
}
}
}
# "max_skill_num" 是自己定义的字段名,之后会在结果中展示
- 分组
# 查询所有数据,并对结果中的 type 进行分组,取分组后的前10条数据
GET hero/_search
{
"query": {
"match_all": {}
},
"aggs": {
"type_group": {
"terms": {
"field": "type",
"size": 10
}
}
}
}
- Java API
private static void aggQuery() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery());
/**
* 组装聚合条件
* name_group:自定义的名字,获取数据使用
* name:分组的字段名
*/
AggregationBuilder aggBuilder = AggregationBuilders.terms("type_group").field("type").size(10);
builder.aggregation(aggBuilder);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取聚合结果
Aggregation groupResult = response.getAggregations().asMap().get("type_group");
// 获取分组结果,需要把 groupResult 转换为 Term
List<? extends Terms.Bucket> buckets = ((Terms) groupResult).getBuckets();
for(Terms.Bucket bucket : buckets){
System.out.println(bucket.getKey() + "----" + bucket.getDocCount());
}
client.close();
}
9. 结果高亮
让查询的结果高亮显示,比如:
其实本质就是设置了一个样式
- Kibana演示
# 对查询结果高亮
GET hero/_search
{
"query": {
"match": {
"skill": "王者荣耀"
}
},
"highlight": {
"fields": {
"skill": {
"pre_tags": "<font color='red'>",
"post_tags": "</font>"
}
}
}
}
- Java API
private static void highLight() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.56.109",9200,"http")));
SearchRequest request = new SearchRequest("hero");// hero:索引名
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("skill","王者荣耀"));
//设置高亮条件
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("skill").preTags("<font color='red'>").postTags("</font>");
builder.highlighter(highlightBuilder);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
SearchHit[] arr = hits.getHits();
for(SearchHit hit : arr){
//获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//拿到高亮的字段
HighlightField addressField = highlightFields.get("skill");
//获取高亮的数据,是一个数组,我们文档比较简单,取数组的第一个就行了
String address = addressField.getFragments()[0].string();
Map<String, Object> map = hit.getSourceAsMap();
//用高亮的数据替换查询到的结果
map.put("skill", address);
System.out.println(map);
}
client.close();
}
3. 索引重建
随着业务的发展,索引的结构可能发生变化。但是 ES 规定,一旦索引创建就只能添加字段,不能修改字段
因为改变字段需要重建:倒排索引,性能太低。
ES提供了另一种方式:重建索引,并把老的索引数据导入到新的索引中
演示:
# 1. 新建一个 student1 索引,只有一个 birth 字段,date类型
PUT student1
{
"mappings": {
"properties": {
"birth":{
"type": "date"
}
}
}
}
# 2. 存储一条数据
PUT student1/_doc/1
{
"birth":"2000-01-01"
}
这时候业务变更,birth字段需要存储 “2000年1月1日”,这样的字符串,如果还用老的索引结构会报错
PUT student1/_doc/2
{
"birth":"2000年1月1日"
}
并且 ES 也不支持修改字段类型
这时候只能重建索引
# 1. 建立一个新的索引:student2, 设置 birth 为 text 类型
PUT student2
{
"mappings": {
"properties": {
"birth":{
"type": "text"
}
}
}
}
# 2. 导入老的索引数据
POST _reindex
{
"source": {
"index": "student1"
},
"dest": {
"index": "student2"
}
}
# 3. 存放一条数据
PUT student2/_doc/2
{
"birth":"2000年1月1日"
}
# 4. 查询数据
GET student2/_search
查询 student2 的数据
但是这时,我们代码中用的还是老索引名,再修改代码明显不现实
ES 提供了给索引设置别名,来解决这种问题:
- 删除老的索引
- 给新的索引设置别名
# 1. 删除老索引
DELETE student1
# 2. 给新的索引设置别名
POST student2/_alias/student1
# 3. 通过别名也能查询到数据
GET student1/_search