博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ElasticSearch与Mysql对比(ElasticSearch常用方法大全,持续更新)
阅读量:2108 次
发布时间:2019-04-29

本文共 22747 字,大约阅读时间需要 75 分钟。

ElasticSearch是一个开源的搜索引擎,它可以被下面这样准确的形容:

  • 一个分布式的实时文档存储,每个字段可以被索引与搜索
  • 一个分布式实时分析搜索引擎
  • 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据

个人理解:

ES的特点就是搜索快;

插入ES的每一条数据可以理解为一个json报文,每个json报文都有一个唯一的id,每次插入就是新增一条json入库;

如果插入时不指定id,则id会自动生成一个新的;

如果插入时指定id,则使用指定的id,并会覆盖之前使用这个id的数据。

ES插入数据前,需要创建索引,ES插入数据时,必须指定索引名;

如果索引存在,则会把数据插入指定索引;

如果索引不存在,则会自动新建一个索引,索引字段为json数据中的字段名。

个人理解,索引就类似数据库中的表。

在此通过对比的方式总结下ES常用方法,从创建索引(表)、插入数据(insert)、删除数据(delete)、修改数据(update)、查询数据(select)等几方面总结。

特别说明:文章所有内容基于ElasticSerch 7.6.2版本;

ES代码样例是在Kibana的Dev Tools中运行的,或者是在Java中运行的。

一、创建索引(表)

1.ES创建索引

ES创建索引,个人感觉就像sql创建数据库中的表。

下方创建一个名称叫exam_data的索引,properties中的为映射(列)名。

ES的创建索引语句例如(POST请求):

PUT exam_data{  "settings":{    "index":{      "number_of_shards": "1",      "number_of_replicas": "1"    }  },    "mappings": {    "properties": {      "userId":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "startTime": {"type":"long"},      "commitTime": {"type":"long"},      "duration": {"type":"double"},      "examStartTime":{"type":"long"},      "examEndTime":{"type":"long"},      "examId":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "examName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "examStyleId":{"type":"long"},      "examStyleName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "isPass":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "passScore": {"type":"double"},      "score": {"type":"double"},      "times": {"type":"long"},      "examCreater":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "examCreateTime": {"type":"long"},      "company":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "com":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "branch":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "brc":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "aracde":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "ara":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "chnl":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "sequence":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},      "memo":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}    }  }}

说明:

这个ES索引(ES表)是用来记录用户考试成绩等相关信息的,用户每完成一次考试,后台就会推到ES一条数据。

字段说明:

userId:用户idstartTime:开始考试时间commitTime:结束考试时间duration:考试用时examStartTime:考试开始时间(可能持续一周)examEndTime:考试结束时间(可能持续一周)examId:考试idexamName:考试名称examStyleId:考试规则id(网段限制等)examSytleName:考试规则名称(网段限制等)isPass:是否通过考试passScore:考试通过所需分数score:考试得分times:考试次数examCreater:考试创建人examCreateTime:考试创建时间company:一级公司idcom:一级公司名称branch:二级公司idbrc:二级公司名称aracde:三级公司idara:三级公司名称chnl:渠道sequence:队列id(全局唯一id,每一条考试记录有一个id)memo:备注

2.对应的建表sql,这里写一个简单的例子。(与上方不完全对应,主要记录下大概用法)

create table 'exam_data' ('id' bigint(20) unsigned not null auto increment,'sequence' bigint(20) unsigned not null comment '队列id','userName' varchar(60) character set utf8mb4 collate utf8mb4_general_ci null default null comment '用户名','type' tinyint(3) unsigned null default null,'start_time' datetime null default null comment '开始时间','end_time' datetime null default null comment '结束时间','create_time' datetime null default current_timestamp comment '创建时间,自动更新','update_time' timestamp null default current_timestamp on update current_timestamp comment '更新时间,自动更新',primary key ('id'),index 'sequence' ('sequence') using btree)engine=InnoDBdefault character set=ut8mb4 collate=utf8mb4_general_cicomment='考试记录表'auto_increment=12398row_format=dynamic;

备注:

create_time与update_time是自动更新的,插入数据时可以不用管这两个字段;

当数据要记录这两个时间时,比较方便;甚至可以在旧表中直接新增这两个字段,而不用管代码逻辑。

二、插入数据

1.ES插入数据样例

例如插入数据到索引exam_data中。

(1)使用put请求,根据索引名与id插入数据

PUT exam_data/_doc/1{  "id123":"123",  "examId":"abc",  "score":"100"}

说明:

exam_data是索引名(表名),_doc是固定写法,1是指定这条json入库后的id为1;

这条json的字段可以不按照索引中设置的映射字段,例如id123并不在创建索引时索引的映射字段(列)中,也不影响之后根据id123搜索出该条数据;

不过这条json会算在索引exam_data下,当删除该索引时,这条json也会被删除。

(2)使用post请求,根据索引名插入数据

POST exam_data/_doc{  "id123":"123",  "examId":"abc",  "score":"100"}

说明:

使用post请求插入数据,不用指定id,插入后会自动生成一个唯一id。

2.sql插入数据样例,简单对比用

insert into mytable(id,name) values('1','abc')

3.Java使用ES插入数据样例

(1)插入数据操作

//注入esclient对象@Autowired@Qualifier("restHighLevelClient")private RestHighLevelClient esClient;public void insertTOES() {  //待插入数据  String data = "{\"id\":\"abc\",\"name\":\"abc\"}";  //这里指定ES的索引名  IndexRequest request = new IndexRequest("exam_data");  //这里指定插入ES的数据的id,也可以不写  //request.id("1");  //填充数据  request.source(data,XContentType.JSON);    //发送请求并获取返回值,esclient是注入的javabean,见下个代码块的创建代码  IndexResponse index = esclient.index(request,RequestOptions.DEFAULT);  //如果插入成功,会返回id;如果指定了id,则值与上方指定的id相同  System.out.println(index.getId());}

(2)esclient对象初始化方法

@Configurationpublic class ElasticsearchRestClientConfig {    //从yml中获取配置信息  @Value("${es.host}")  private String host;  @Value("${es.port}")  private String port;  @Value("${es.connTimeout}")  private String connTimeout;  @Value("${es.socketTimeout}")  private String socketTimeout;  @Value("${es.connectionRequestTimeout}")  private String connectionRequestTimeout;  @Value("${es.username}")  private String username;  @Value("${es.password}")  private String password;  //把这个bean转入spring容器,之后就能@Autowired获取到了  @Bean  public RestHighLevelClient restHighLevelClient(){    BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();    credentialsProvider.setCredentials(AuthScope.ANY,new UsernamePasswordCredentials(username,password));    RestClientBuilder builder = RestClient.builder(new HttpHost(host,port))      .setRequestConfigCallback(requestconfigBuilder -> requestConfigBuilder        .setConnectTimeout(connTimeout)        .setSocketTimeout(socketTimeout)        .setConnectionRequestTimeout(connectionRequestTimeout)      );    return new RestHighLevelClient(builder);  }}

(3)相关application.yml

#es相关参数,名称不固定,自己用的es:   host: 10.123.123.123  port: 9200  scheme: http  connTimeout: 5000000  socketTimeout: 3000000  connectionRequestTimeout: 500  #用户名与密码,ES允许不设置用户名密码登录,不过不安全;所以最好还是设置一个  username: abc  password: abc

三、删除数据

1.ES删除数据

(1)根据查询条件删除

POST exam_data/_delete_by_query{  "query": {    "term": {      "examId": "abc"    }  }}

说明:

exam_data是索引名,_delete_by_query是固定写法;

该语句删除所有examId等于abc的数据。

(2)根据id删除

DELETE exam_data/_doc/1

该语句根据每条数据的唯一id删除数据。

删除了id为1的数据。

2.sql删除数据样例

delete from exam_data where id = '1'

四、修改数据

1.ES修改数据

(1)先找到目标数据的id,然后新增一条相同id的数据,即可覆盖掉旧数据

(2)先找到目标数据的id,然后进行修改

POST exam_data/_update/1{  "doc": {    "examId":"abcdefg"  }}

这个语句将id为1的数据的examId修改为abcdefg.

(3)按照查询条件进行修改

POST exam_data/_update_by_query{  "query":{    "term":{      "examId":"abcdefg"    }  },  "script":{    "source": "ctx._source['concact']=\"ccc\""  }}

这个语句,将把examId为abcdefg的所有数据的concact字段修改为ccc;如果没有concact字段,则会新增该字段并设置为ccc;

_update_by_query为固定写法(更新by查询),ctx._source为固定写法(指向当前要被更新的文档)。

2.sql修改数据

update exam_data e set e.concact = 'ccc' where e.examId = 'abcdefg'

五、查询数据

1.ES查询数据

GET exam_data/_search{  "query":{    "bool": {      "must" : [        {          "term": {             "examId.keyword": {                "value": "abcdefg"             }          }        }      ],      "adjust_pure_negative": true,      "boost": 1    }  }}

这个语句可以匹配到所有examId为abcdefg的数据,返回结果默认返回10条(设置方法不在此处讲解),返回的hits字段是匹配数量,可以知道究竟有多少条数据符合此条件。

2.sql查询数据

select * from exam_data where examId = 'abcdefg'

六、ES复杂场景查询样例

1.根据id或名称,模糊查询数据,如果有多条重复,选取最新的一条。

分两步,先模糊查询,获得最新的id,然后根据此id获取其余数据。(ES一步到位较难,因此使用这个方法)

ES使用嵌套bool、aggregations聚合实现。

(1)对应sql:

select id from exam_data where (examName like '%考试%' or examId like "%考试%") order by create_time desc limit 1
select * from exam_data where id = '查询得到的id'

(2)对应ES:分两步,第一步,根据传入的值模糊查询,得到最新一条的id。

GET exam_data/_search{  "query": {    "bool": {      "must": [        "bool": {          "should": [             {               "match_phrase_prefix":{                  "examName": {                     "query": "考试",                     "slop": 0,                     "max_expansions": 50,                     "boost": 1                  }               }             },             {               "match_phrase_prefix":{                  "examId": {                     "query": "考试",                     "slop": 0,                     "max_expansions": 50,                     "boost": 1                  }               }             }          ],          "adjust_pure_negative": true,          "boost": 1        }      ],      "adjust_pure_negative": true,      "boost": 1    }  },  "aggregations": {     "examStartTime": {       "terms": {         "field": "examStartTime",         "size": 1,         "min_doc_count": 1,         "shard_min_doc_count": 0,         "show_term_doc_count_error": false,         "order":{            "_key": "desc"         }       },       "aggregations": {          "examId": {            "terms": {              "field":"examId.keyword",              "size":10,              "min_doc_count": 1,              "shard_min_doc_count": 0,              "show_term_doc_count_error": false,              "order":[                {                  "_count": "desc"                },                {                  "_key": "asc"                }              ]            }          }       }     }  }}

说明:

只用2个should不能实现需求,必须使用bool嵌套should才行,如上方使用了2个bool。

order中的_key:desc可以按key的降序排序,例如examStartTime(long)的降序;再有size:1,就是最大的了。

第一次聚合是为了按时间聚合、降序、得到时间最新的数据;第二次聚合是为了得到examId;只用第一次聚合的话,ES返回的内容中只有examStartTime和聚合数,得不到examId,所以要用第二次聚合。

ES第二步,根据id查询目标数据,如果为空也没关系,查询到0条就可以:

GET exam_data/_search{  "query": {    "bool": {      "must": [        {           "term": {             "examId":{                 "value": "得到的id",                 "boost": 1             }           }        }      ],      "adjust_pure_negative": true,      "boost": 1    }  },  "aggregations": {          "zrs": {            "cardinality": {              "field":"userId.keyword"           }         }   }}

说明:ES中,如果要获取某条数据的某个信息,最好还是使用聚合aggregations,聚合某字段,然后从桶里找值,而不是从hits中找,因为hits返回的有时候不准确。

上方的语句,筛选条件为某个指定的examId,然后用cardinality对结果的userId去重,可以从返回的结果桶中得到考试人数。

(3)对应java,功能为根据考试id或考试名称,获取到最新的考试的考试人数:

private static String getLatestIdByIdOrName(String examNameOrId, RestHighLevelClient esclient) throws Exception{String examNameOrId = "考试";//模糊查询ES,获取最新的考试idString[] result = new String[1];BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();BoolQueryBuilder boolShould = QueryBuilders.boolQuery();boolShould.should(QueryBuilders.matchPhrasePrefixQuery("examName",examNameOrId));boolShould.should(QueryBuilders.matchPhrasePrefixQuery("examId",examNameOrId));boolQueryBuilder.must(boolShould);//聚合,按时间逆序,选1条就够TermsAggregationBuilder termsTime = AggregationBuilder.terms("examStartTime").field("examStartTime").order(InternalOrder.key(false).size(1));//聚合,用来获取examId的聚合TermsAggregationBuilder termsId = AggregationBuilder.terms("examId").field("examId");termsTime.subAggregation(termsId);//从结果找到examId, getSearchResponse是自己封装的方法,见下SearchResponse searchResponse = getSearchResponse(esclient, boolQueryBuilder, termsTime);ParsedLongTerms parsedExamStartTime = searchResponse.getAggregations().get("examStartTime");parsedExamStartTime.getBuckets().forEach( bucket -> {  ParsedLongTerms parseExamId = bucket.getAggregations().get("examId");  parseExamId.getBuckets().forEach( bucket2 -> {    //因为设置了size为1,所以正常情况下只循环一次    result[0] = bucket2.getKeyAsString();  });});return result[0];}

getSearchResponse()方法:

待完善。

然后根据id,按userId聚合,查询考试人数:

待完善。

七、其它相关笔记,ES条件查询、ES聚合等样例

待完善

八、开发中遇到的问题与解决方法

本次开发了一个数据统计系统(test-cockpit),用到了ES;每个考生考完一次考试,就会把消息推送给ES;然后有一个前端页面可以展示按要求从ES查询到的数据,例如考试数、考试人数、考试次数、考试时长等。

遇到的问题与解决方法如下:

1.swigger,在后端使用这个jar包后,可以访问指定网页,得到后端现有的controller的url与出参入参,便于前端查看;

springboot项目中,默认访问的网页是:http://localhost:8080/swagger-ui.html

(或:http://localhost:8080/swagger/index.html)

2.kafka不用在控制台手动创建队列,直接使用即可;但是使用新队列时,需要生产者先向该队列发送一条消息,然后消费者才能启动成功;如果消费者先启动并监听新队列,会报错(队列不存在导致):

nested exception is java.lang.IllegalStateException: Topic(s) [test-my_kafka_Topic] is/are not present and missingTopicFatal is true

3.将父类bean转换为子类bean的方法

(1)利用json

String jsonStr = JSONObject.toJSONString(fatherBean);ChildBean cb = JSONObject.parseObject(jsonStr, ChildBean.class);

(2)使用BeanUtils

BeanUtils.copyProperties(fatherBean, childBean);

4.输出流输出的数据不正确问题

使用输出流输出excel文件时,用到了ServletOutputStream,发现使用该对象输出时,必须使用3个参数的输出方法,输出内容才正确;不能使用1个参数的输出方法,输出的内容打不开。如下:

FileInputStream inputStream = null;ServletOutputStream outputStream = null;try{//假设从硬盘读excel,准备返回给用户inputStream = new FileInputStream("D://abc.xls");int l;//jar包中就是1024,所以这个没问题byte[]i = new byte[1024];//这个response是controller中的ServletHttpResponseoutputStream = response.getOutputStream();while((l = inputStream.read(i)) != -1){  //这里必须是三个参数,输出才正确;否则输出的excel打不开  outputStream.write(i,0,l);    //这个不行  //outputStream.write(i);}outputStream.flush();logger.info("导出成功");return "导出成功";}catch(Exception e){  logger.error("导出失败!",e);  return "导出失败,请稍后再试!";}//标准关流方法finally {if(outputStream != null){  try{    outputStream.close();  }catch(IOException e){    logger.error("outputStream关闭失败");  }}if(inputStream != null){  try{    inputStream.close();  }catch(IOException e){    logger.error("inputStream关闭失败");  }}}

5.获得机构树的一种方法

(1)数据库表中,存储着三级的机构树,例如:

id company com branch brc aracde ara
1 9 北京分公司 NULL NULL NULL
23 9 北京分公司 15 朝阳 NULL NULL
24 9 北京分公司 16 昌平 NULL NULL
33 9 北京分公司 16 昌平 102 阳高中路支公司
34 9 北京分公司 16 昌平 103 曹路支公司

想要获得机构树层级的map,首先要在sql中按规则查询,然后java里处理;

(2)sql如下:

select * from (select * from company where branch ="" and aracde ="NULL" order by company asc) a union(select * from company where branch !="" and aracde ="NULL" order by branch asc) b union(select * from company where branch !="" and aracde !="NULL" order by aracde asc) c

这个sql可以查询出一级分公司、二级分公司、三级分公司,然后用union,把结果集按顺序拼起来,最后返回一个List<Company>对象。(Company是javabean,对应数据库的company表)

(3)java如下:

public List selectCompanyTree(){  //通过respository,执行sql,获取结果  List
companys = companyRespository.findCompanyTree(); //准备返回的机构树 ArrayList
treeList = new ArrayList<>(); //准备一个map HashMap
instituteVoMap = new HashMap<>(); //开始处理返回结果,返回结果有序,第一部分为一级,第二部分为二级,第三部分为三级 for(Company c : companys){ //如果是一级 if(StringUtils.isEmpty(String.valueOf(c.getBranch()).trim()) && "NULL".equals(c.getAracde())){ //先判断map中是否存在,如果不存在才处理,如果存在,说明处理过了,不做处理 if(instituteVoMap.get(c.getCompany()) == null){ instituteVo level1Vo = new instituteVo(c.getCompany(),c.getCom(),new ArrayList<>()); //存入map instituteVoMap.put(c.getCompany(),level1Vo); //存入返回的list treeList.add(level1Vo); } } //如果是二级 else if(StringUtils.isNotEmpty(String.valueOf(c.getBranch()).trim()) && "NULL".equals(c.getAracde())){ //先判断map中是否存在,如果不存在才处理,如果存在,说明处理过了,不做处理 if(instituteVoMap.get(c.getCompany()+"_"+c.getBranch()) == null){ instituteVo level2Vo = new instituteVo(c.getCompany()+"_"+c.getBranch(),c.getBrc(),new ArrayList<>()); //获取一级的对象 instituteVo level1Vo = instituteVoMap.get(c.getCompany()); //如果一级对象还没有处理,那就特殊处理,创建一个 if(level1Vo == null){ instituteVo level1Vo = new instituteVo(c.getCompany(),c.getCom(),new ArrayList<>()); //存入map instituteVoMap.put(c.getCompany(),level1Vo); //存入返回的list treeList.add(level1Vo); } //装入sublist level1Vo.getChildren().add(level2Vo); //存入map instituteVoMap.put(c.getCompany()+"_"+c.getBranch(),level2Vo); } } //如果是三级 else if(StringUtils.isNotEmpty(String.valueOf(c.getBranch()).trim()) && !"NULL".equals(c.getAracde())){ instituteVo level3Vo = new instituteVo(c.getCompany()+"_"+c.getBranch()+"_"+c.getAracde(),c.getAra(),null); //找到二级的对象 instituteVo level2Vo = instituteVoMap.get(c.getCompany()+"_"c.getBranch()); //如果二级对象还没有处理,就特殊处理 if(level2Vo == null){ level2Vo = new instituteVo(c.getCompany()+"_"+c.getBranch(),c.getBrc(),new ArrayList<>()); //找到一级的对象 instituteVo level1Vo = instituteVoMap.get(c.getCompany()); //如果一级对象还没有处理,那就特殊处理,创建一个 if(level1Vo == null){ instituteVo level1Vo = new instituteVo(c.getCompany(),c.getCom(),new ArrayList<>()); //存入map instituteVoMap.put(c.getCompany(),level1Vo); //存入返回的list treeList.add(level1Vo); } //存入map instituteVoMap.put(c.getCompany()+"_"+c.getBranch(),level2Vo); //把二级存入一级的子list level1Vo.getChildren().add(level2Vo); } //把三级存入二级的子list level2Vo.getChildren().add(level3Vo); }else { logger.error("未知级别的机构"+c.toString()); } } //写一个全部,当做最高级别的树 ArrayList
resultList = new ArrayList<>(); InstituteVo masterVo = new InstituteVo("","全部",treeList); resultList.add(masterVo); return resultList; }}

总结:

通过sql的union,把一级、二级、三级机构树按顺序排好;然后在java中,使用一个map缓存机构对象,把一级、二级、三级对象都存入map中,然后把三级对象存入对应的二级对象的子链中(list)、把二级对象存入对应的一级对象的子链中(list);

然后把一级对象装入一个list返回,或者在外层再套一个"全部"机构后、装入list返回。

6.sql按天导出数据,没有的补充0

如果sql直接按天导出数据,【group by DATE_FORMAT(t.create_time,'%Y-%m-%d')】,当某天有数据时,没有问题;当某天没有数据时,就没有那行;不符合要求。

可以用以下方式实现:

(1)使用sql创建一个包含每天的结果集,结果集只有一列,为每天的数据,如

date
2021-07-01
2021-07-02
......

sql如下:

select date from (  select ADDDATE('1970-01-01', t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) date from  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) vwhere date between '2021-07-01' and '2021-07-30'

这句sql可以查询出列名为date、数据为2021-07-01到2021-07-30的结果集;

这句sql中用逗号连接t0,t1,t2,t3,t4,得到笛卡尔积,然后用ADDDATE方法,算出每行的日期并转为【年-月-日】格式(每行的日期差1天,adddate('1970-01-01',1)会得到1970-01-02);

最后使用where得到所要的范围。

(2)使用上方的sql,left join 查询出数据的结果,使用ifnull为没有的赋值为0,即可得到所需的数据;如下:

select date_table.date as final_date, ifnull(data_table.group_count, 0) as final_count from(select date from (  select ADDDATE('1970-01-01', t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) date from  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,  (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) vwhere date between '2021-07-01' and '2021-07-30') date_tableleft join (select date_format(a.create_time,'%Y-%m-%d') as group_date, count(*) as group_count from exam_data a group by date_format(a.create_time,'%Y-%m-%d')) data_tableon date_table.date = data_table.group_dateorder by final_date

这句sql先查询出日期列结果集、数据按天分组后的结果集,然后用left join关联、没有的补0,最后得到想要的结果。

最后的order by是必要的,为了让结果有序,如果不加,有时候顺序会乱。

7.java按天导出数据,没有的补充0

以下是java代码:

//某天没有数据的,补0public static void fillDate(String startTime, String endTime, ArrayList
result) throws Exception{ //假设传入参数的样式 startTime = "2021-07-01"; endTime = "2022-08-08"; MyBean my1 = new MyBean("2021-07-01","5"); MyBean my2 = new MyBean("2021-07-03","3"); MyBean my3 = new MyBean("2021-07-05","1"); //该list中的数据需要是有序的,日期从小到大 result = new ArrayList<>(); result.add(my1); result.add(my2); result.add(my3); //准备补从开始日期到结束日期、没有数据的为0 //例如new MyBean("2021-07-02","0")然后装入list等 if(StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime)){ //准备返回的list ArrayList
newResult = new ArrayList<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date startDate = sdf.parse(startTime); Date endDate = sdf.parse(endTime); //借助calendar,从开始日期,每次加1,进行补 Calendar c = Calendar.getInstance(); c.setTime(startDate); Iterator
iterator = result.iterator(); MyBean fromListBean = null; if(iterator.hasNext()){ fromListBean = iterator.next(); } //当没有到最后一天时 while(c.getTime().getTime() <= endDate.getTime()){ //如果list为空 if(fromListBean == null){ MyBean bean = new MyBean(); bean.setDate(sdf.format(c.getTime())); bean.setValue("0"); newResult.add(bean); } else{ String date = fromListBean.getDate(); String compareDate = sdf.format(c.getTime()); //如果日期相等 if(StringUtils.equals(compareDate,date)){ newResult.add(fromListVo); //然后指向下一个元素,如果没有了,设为null if(iterator.hasNext()){ fromListBean = iterator.next(); }else{ fromListBean = null; } } //如果不相等,newBean,补0 else{ MyBean bean = new MyBean(); bean.setDate(sdf.format(c.getTime())); bean.setValue("0"); newResult.add(bean); } } //加一天 c.add(Calendar.DAY_OF_MONTH, 1); } //使用新的list; 没有写return,所以这样写 result.clear(); result.addAll(newResult); }}

8.sql条件查询!=的问题

sql中,使用where a.flag !='0',期望得到flag不是0的所有结果,但是并不行,flag=null也不是0,却被去掉了;所以应该使用where ifnull(a.flag,1)!='0',才能得到flag不是0的所有结果;

或者使用where if(a.flag is null,'1',a.flag) != '0',也可以得到flag不是0的所有结果。

9.sql时间相减问题

sql中的dateTime等时间类型不能直接相减,结果不对;需要使用TIME_TO_SEC()转为秒然后再减。

10.ES匹配*的问题

如果某个字段的值恰好为*,那么以下查询会有问题:

GET exam_data/_search{  "query":{    "bool":{      "must_not": [        {          "term": {            "examId": {              "value": "*"            }          }        }      ]    }  }}

这条语句想查询所有examId不为*的数据,但是会不起作用,查询出所有数据。

同样,以下查询也有问题:

GET exam_data/_search{  "query":{    "bool":{      "must": [        {          "term": {            "examId": {              "value": "*"            }          }        }      ]    }  }}

该语句想查询所有examId为*的数据,但是也有问题,会查询到0条数据。

解决方法:

将examId替换为examId.keyword,才能实现想要的效果。

转载地址:http://nxuef.baihongyu.com/

你可能感兴趣的文章
(PAT 1118) Birds in Forest (并查集)
查看>>
数据结构 拓扑排序
查看>>
(PAT 1040) Longest Symmetric String (DP-最长回文子串)
查看>>
(PAT 1145) Hashing - Average Search Time (哈希表冲突处理)
查看>>
(1129) Recommendation System 排序
查看>>
PAT1090 Highest Price in Supply Chain 树DFS
查看>>
(PAT 1096) Consecutive Factors (质因子分解)
查看>>
(PAT 1019) General Palindromic Number (进制转换)
查看>>
(PAT 1073) Scientific Notation (字符串模拟题)
查看>>
(PAT 1080) Graduate Admission (排序)
查看>>
Play on Words UVA - 10129 (欧拉路径)
查看>>
mininet+floodlight搭建sdn环境并创建简答topo
查看>>
【linux】nohup和&的作用
查看>>
【UML】《Theach yourself uml in 24hours》——hour4
查看>>
Set、WeakSet、Map以及WeakMap结构基本知识点
查看>>
【NLP学习笔记】(一)Gensim基本使用方法
查看>>
【NLP学习笔记】(二)gensim使用之Topics and Transformations
查看>>
【深度学习】LSTM的架构及公式
查看>>
【深度学习】GRU的结构图及公式
查看>>
【python】re模块常用方法
查看>>