【Elasticsearch】查与部分增

前言:
最近感觉有好多事情要做,时间真是越来越珍贵啦

正文:
    首先Elasticsearch(以下简称es)是一个基于Lucene的搜索服务器,顾名思义是查询方面的一个能手,但是他也是有一套自己的增删改的、相对而言用处较少,也比较简单所以简单略过啦:

    增:指定的 /Index/Type/id 发送 PUT 请求
        可不指定id,POST

    删:curl -X DELETE 'localhost:9200/accounts/person/1'

    更新:PUT 请求,重新发送一次数据

    查:/Index/Type/Id发出 GET 
    搜索/Index/Type/_search 返回值:
        total:返回记录数,本例是2条。
        max_score:最高的匹配程度,本例是1.0。
        hits:返回的记录组成的数组。

用于操作的curl格式如下:

curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'

 1、VERB:http方法,get、post、put、head或delete(我是大写的)
 2、PROTOCOL:http或https
 3、HOST:es集群中任意节点的主机名,可以用localhost代本机节点
 4、PATH:API的终端路径
 5、QUERY_STRING:任意可选的查询字符串参数 (如 ?pretty 将输出 JSON 返回值)
 6、BODY:一个 JSON 格式的请求体 (不是必须)

curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
    "query": {
        "match_all": {}
    }
}
'

为了撑得起搜索服务器的名号,es为查询提供了丰富的支持:

常用的term查询:

      不分词地查询某字段里有(一个或多个)关键词的文档

#查询preview字段有elasticsearch或book关键词的文档(从1开始返回2个结果)

#minimum_match:最小匹配集,1最少有一个匹配,只要有一个出现在preview中文档将被查询出来,如为2 则priview中两个词都要有才能被选中

GET /Index/type/_search
{
    "from":1,
    "size":2,
    "query":{
        "term":{
            "preview":["elasticsearch","book"],
            "minimum_match":1
        }
    }
}
简体:
GET /index/type/_search?q=preview:elasticsearch
match查询:

      会根据自指定的字段提供合适的分词器(安装分词器或用自带的)

GET /Index/type/_search{
    "query":{
        "match":{
            "preview":"elasticsearch"
        }
    }
}

还有match_phrase,其slop指定关键词之间要相隔多少个词,这个就不写了,占地;

常用的MySQL中有group by、order by、and 、or、not等,es中有没有相似的语法呐?既然我问了那应该是有的

{
    "bool":{
        "must":[],//and
        "should":[],//or
        "must_not:[]//not
    }
}
#官网上贴过来个看起来高大上的例子
POST _search
{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user" : "kimchy" }
      },
      "filter": {//filter通:过滤,符合规则 则添加到桶中,只影响聚合
        "term" : { "tag" : "tech" }
      },
      "must_not" : {//这个在bool里面,就是嵌套查了
        "range" : {//范围过滤,gte是大于等于、lte小于等于、gt 大于 lt:小于
          "age" : { "gte" : 10, "lte" : 20 }
        }
      },//bool结束
      "should" : [
        { "term" : { "tag" : "wow" } },
        { "term" : { "tag" : "elasticsearch" } }
      ],
      "minimum_should_match" : 1,//最少应该匹配1个
      "boost" : 1.0//类似权重,给符合添加的结果设定权重
    }
  }
}

另一种使用and、or、not查询:

{
    "filtered" : {
        "query" :   { ... },//默认match_all
        "filter" :  { ... },
        "strategy": "leap_frog"
    }
}
#实例如下
#The filtered query is passed as the value of the query parameter in the search request.
#在搜索请求中过滤后的查询作为查询参数
GET /index/type/_search
{
    "query":{
        "filtered":{
            "filter":{
                "bool":{##or
                    "should":[
                        {"term":{"productID":"124566"}},
                        {
                            "bool":{
                                "must":[##and
                                    {"term":{"productID":"24345"}},
                                    {"term":{"price":20}}
                                ]
                            }
                        }
                    ]
                }
            }
        }
    }
}
#翻译成SQL就是:
select document from products where productID = "124566" or (productID="24345" and price=20) 

post_filter:后置过滤器,查询完毕过滤搜索结果;只和聚合一起使用且使用了不同的过滤条件时使用

分组聚合aggs:

      对一份数据执行分组聚合,这个aggs里面可以进一步操作:count、avg、max、min等;

GET /tvs/sales/_search
{
        "size" : 0,##只获取聚合结果,而不要执行聚合的原始数据
        "aggs" : { 
        "popular_colors" : { ##给每个aggs起的名字,随便取什么都ok
             "histogram": {#按照给定值进行分组,price以2000为间隔分组
                "field": "price",
                "interval": 2000   #2000一个区域,数据对应放入
              },
             "terms" : { 
                 "field" : "color"##根据指定的字段color的值进行分组
              },
              "aggs": {//嵌套、专业点叫做下钻分析,多个维度、多次分组
                   "avg_price": {
                     "avg": {//取price的平均数avg,可以min、max、sum或count
                       "field": "price"
                     },
                      "cardinality": {#去重,具体:对brand去重
                        "field": "brand" 
                      }
                  }
               }
          }
     }
}
#相当于:Select avg(price) from  tvs.sales group by color
结果有一个默认的排序规则:
    按照doc_count降序排序,doc_count是es中bucket操作默认执行的一个内置metric(聚合统计操作:maxmin、count、avgsum)

此外还支持地理位置的查询,这个挺简单的,上网查吧;

增的话也挺简单的,用PUT和POST,可以插入一条也可以多条

PUT /website/blogs/1
{
  "title":    "我的第一篇博客",
  "content":     "这是我的第一篇博客,开通啦!!!",
  "userId":     1 
}
#多条
POST /company/rd_center/_bulk
{ "index": { "_id": "1" }}
{ "name": "北京研发总部", "city": "北京", "country": "中国" }
{ "index": { "_id": "2" }}
{ "name": "上海研发中心", "city": "上海", "country": "中国" }
{ "index": { "_id": "3" }}
{ "name": "硅谷人工智能实验室", "city": "硅谷", "country": "美国" }
JAVA API

     首先连上es:这个连接方式开始的代码一直报红线,弄了好长时间,刚开始接触es、又有任务在身,我真是要崩溃了,后来在社区里面shiyuan给了我下面的连接方式,当然es不只这一种连接方式,当时给我的代码同一个类import了两个包,所以悲剧了很长时间;在es中文社区得到了很多帮助,很谢谢大佬们的帮助,特别是laoyang360,现在看看自己问了很多不该问的问题,只怪那是太年轻

 public TransportClient Client() throws UnknownHostException {
        Settings settings = Settings.builder()
                .put("cluster.name", "elasticsearch")
                .put("client.transport.sniff", true)
                .build();
        TransportClient client = new PreBuiltTransportClient(settings)
                .addTransportAddress(new TransportAddress(InetAddress.getByName("192.168.22.95"), 9300));
        return  client;
    }

上面获得了集群或者es服务器的连接,接下来该index:

//如果index存在则插入 不存在则创建,这只是一种形式,兼顾建与增所以写了出来
IndexRequest indexRequest = new IndexRequest("car_shop", "cars", "1")
                .source(XContentFactory.jsonBuilder()
                        .startObject()
                        .field("brand", "宝马")
                        .field("name", "宝马320")
                        .field("price", 310000)
                        .field("produce_date", "2018-04-01")
                        .endObject()
                );

 UpdateRequest updateRequest = new UpdateRequest("car_shop", "cars", "1")
                .doc(XContentFactory.jsonBuilder()
                        .startObject()
                        .field("price", 310000)
                        .endObject())
                .upsert(indexRequest);

UpdateResponse updateResponse = client.update(updateRequest).get();
System.out.println(updateResponse.getVersion());//锁机制也用到了这个版本控制
client.close();

Elasticsearch的jar包为我们提供了很丰富的增删改特别是查的方法,如图
调用方法
可以点出来很多方法,根据需要使用
目前使用的是:boolQuery

QueryBuilder queryBuilder = QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("brand","宝马"))//must
                .mustNot(QueryBuilders.termQuery("name.raw","宝马313"))//must_not
                .should(QueryBuilders.rangeQuery("produce_date").gte("2017-02-04").lte("2017-09-09"))//should
                .should(QueryBuilders.rangeQuery("price").gte(28000).lte(35000));//should

 SearchResponse searchResponse = client.prepareSearch("car_shop")//index
                .setTypes("cars")//type
                .setQuery(queryBuilder)//类似参数
                .get();//执行
  //response,这个应该能猜出来是什么吧,hits击中
for (SearchHit searchHit : searchResponse.getHits().getHits()) {
            System.out.println(searchHit.getSourceAsString());
}

还有disMaxQuery和curl格式、DSL查询差不多、灵活运用吧,学习中……:

 QueryBuilder disMaxQueryBuilder = QueryBuilders.disMaxQuery()
              .add(QueryBuilders.matchQuery("title","java solution"))
              .add(QueryBuilders.matchQuery("content","java solution"));

SearchResponse searchResponse = client.prepareSearch("index")
            .setTypes("type")
            .setQuery(disMaxQueryBuilder)
            .get();
 client.close();

多索引查询:

MultiGetResponse multiGetesponses = client.prepareMultiGet()
                    .add("car_shop", "cats", "1")
                    .add("car_shop", "cars", "2")
                    .get();
for (MultiGetItemResponse multiGetItemResponses : multiGetesponses) {
       GetResponse getResponse = multiGetItemResponses.getResponse();
             if (getResponse.isExists()) {
                System.out.println(getResponse.getSourceAsString());
             }
         }
client.close();

client这个也是可以查的:

SearchResponse searchResponse = client.prepareSearch("car_shop")
                .setTypes("cars")
                .setQuery(QueryBuilders.matchQuery("brand","宝马"))
                .setSize(1)//每个primary分片返回的文档数 
                .get();
searchResponse = client.prepareSearch("car_shop")
                .setTypes("cars")
                .setQuery(QueryBuilders.multiMatchQuery("宝马","brand","name"))//brand和name中有 宝马 这个值的
                .get();
searchResponse = client.prepareSearch("car_shop")
                .setTypes("cars")
                .setQuery(QueryBuilders.termQuery("name.raw", "宝马318"))
                .setScroll(new TimeValue(6000))//滚动的有效时长:保持搜索的上下文环境多长时间
                .get();
searchResponse = client.prepareSearch("car_shop")
                .setTypes("cars")
                .setQuery(QueryBuilders.prefixQuery("name", "宝"))//name以宝字开头的
                .get();

搜索请求一次请求最大量为[10000],但是为了请求大量数据应该怎么办呐?在Scroll帮助下我们可以做一个初始阶段搜索并且持续批量地从es中获取结果直到没有结果剩下,这有点像传统数据库里的cursors(游标)。
——————————————————————————————————————————
我们可以看出Scroll API的创建并不是为了实时的用户响应,而是为了处理大量的数据。从 scroll 请求返回的结果只是反映了 search 发生那一时刻的索引状态,就像一个快照。

我们通过SearchResponse对象的getScrollId()方法获取滚动ID;滚动ID将在下一次请求中使用,这个 ID 可以传递给 scroll API 来检索 下一个批次的结果,每次返回下一个批次结果 直到没有结果返回时停止

  do {
            for (SearchHit searchHit : searchResponse.getHits().getHits()) {
                System.out.println("batch" + ++batchCount);
                System.out.println(searchHit.getSourceAsString());
            }
            searchResponse = client.prepareSearchScroll(searchResponse.getScrollId())
                    .setScroll(new TimeValue(6000))
                    .execute()
                    .actionGet();
        } while (searchResponse.getHits().getHits().length != 0);

        client.close();

es命名很不错,看到名字大概就知道了他的作用,这样挺好,也就不做过多的解释了;
参考 参考 参考
滚动搜索(Scroll API)

如果获取位置,这个要怎么办呐?

//搜索两个坐标点组成的一个区域
SearchResponse searchResponse = client.prepareSearch("car_shop")
              .setTypes("shops")
              .setQuery(QueryBuilders.geoBoundingBoxQuery("pin.location")
                     .setCorners(40.74, -72.1, 30.43, -72.44))
              .get();

//指定一个区域,由三个坐标点,组成,比如上海大厦,东方明珠塔,上海火车站
 List<GeoPoint> points = new ArrayList<GeoPoint>();
 points.add(new GeoPoint(40.73, -74.1));
 points.add(new GeoPoint(40.01, -71.12));
 points.add(new GeoPoint(50.56, -90.58));
 searchResponse = client.prepareSearch("car_shop")
       .setTypes("shops")
       .setQuery(QueryBuilders.geoPolygonQuery("pin.location", points))
       .get();

//搜索距离当前位置在200公里内的4s店
searchResponse = client.prepareSearch("car_shop")
           .setTypes("shops")
           .setQuery(QueryBuilders.geoDistanceQuery("pin.location")
                   .point(40, -70)
                   .distance(200, DistanceUnit.KILOMETERS))
           .get();
 client.close();

利用模板查询也是es的一大特色(新建文件,保存到config/script目录下的后缀名是.mustache文件),有一个复用的思想:

 Map<String,Object> scriptParam = new HashMap<String,Object>();
 scriptParam.put("from",0);//模板所需参数
 scriptParam.put("size",1);
 scriptParam.put("brand","宝马");

//读取 模板文件 上面是参数  第一个是名字、类型、参数、索引……
SearchResponse searchResponse = new SearchTemplateRequestBuilder(client)
                .setScript("page_query_by_brand")//模板名称
//              .setScriptType(ScriptType.FILE)
//              .setScriptParams(scriptParams)
                .setRequest(new SearchRequest("car_shop").types("sales"))
                .get()
                .getResponse();

模板大概长这个样子:

{
  "query": {
    "bool": {
      "must": {
        "match": {
          "line": "{{text}}" 
        }
      },
      "filter": {
        {{#line_no}} 
          "range": {
            "line_no": {
              {{#start}} 
                "gte": "{{start}}" 
                {{#end}},{{/end}} 
              {{/start}} 
              {{#end}} 
                "lte": "{{end}}" 
              {{/end}} 
            }
          }
        {{/line_no}} 
      }
    }
  }
}

小结:
    es虽然有增删改查,不过单一职责嘛建议大家还是用他来查询,其他的事情可以让数据库或缓存来做,要学习的地方还要很多,感觉自己还没有入门,这些知识虽然写了博客,但是很尴尬。

[死磕Elasitcsearch]知识星球地址:http://t.cn/RmwM3N9;微信公众号:铭毅天下; 博客:https://blog.csdn.net/laoyang360

【中华石杉个人首页】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值