Elasticsearch快速入门2 - 高级查询功能

我们在Elasticsearch快速入门1中详细介绍了ES的安装、基本概念和一些基本的REST Api请求,在这篇入门(2)中,我们继续介绍ES的高级查询功能。

为了说明ES强大的搜索功能,我们还以上篇文章中的customer索引为例,但对其中的文档字段进行了一定的补充,补充后一个文档的内容大致如下所示:

{
    "firstname": "zhang",
    "lastname": "san",
    "age": 29,
    "gender": "F",
    "address": "某某区某某街某某小区某号楼某单元某零几",
    "email": "san.zhang@qq.com",
    "city": "北京"
}

在ES中有两种方式可以进行高级查询,一种是通过在REST request URI中传递参数,另一种是通过REST request body来传递查询参数。因为第二种方式更富有表现力、不受URI长度的限制并且使用了更加易读的JSON格式来表示,因此实际应用中多数以request body的形式来查询,我们这里也不再对第一种方式进行过多的介绍。

如果想通过REST API来使用查询功能,则必须要在URI的最后添加_search关键字,并且不需要再指定类型(type)。

match_all

首先来看下怎么查询customer索引下的所有文档:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} }
}
'

query关键字说明了我们希望进行的是查询操作,它的值就是要查询的条件。上面的请求返回的数据我们摘录一部分内容,如下所示:

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.0,
    "hits" : []
  }
}

其中,每个字段的含义是:

  • took – ES执行查询操作用时,单位为milliseconds
  • timed_out – 查询是否超时
  • _shards – 查询了几个分片,每个分配查询的结果是什么
  • hits – 查询的结果
  • hits.total – 匹配我们搜索条件的文档数量
  • hits.hits – 查询结果的数组,默认是取前10个文档
  • hits.sort – 对结果进行排序的字段,如果没有排序这个字段为空
  • hits._score 和 max_score – 文档与指定查询的相关性,越高说明相关性越大

sort

如果我们想对结果进行排序,可以使用sort关键字:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}
'

因为可以根据多个字段进行排序,因此sort关键字对应的是一个数组,允许我们指定多个排序策略。

size、from

我们在介绍返回结果字段含义的时候说过,默认是取前10个文档,如果想修改这个值可以指定size参数,比如只取一个文档:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "size": 1
}
'

默认情况下,上面的请求返回的是从第0个文档算起的,同样,我们也可以修改这个值,比如取第11个到第20个文档,就要指定另外一个值from

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "from": 10,
  "size": 10
}
'

有一点需要强调的是,from这个参数是从0开始的,它表明了希望从哪里开始截取数据,size参数指定了要返回多少文档,因此,通过利用fromsize,我们可以方便的实现分页操作。

_source

跟其他的数据库操作一样,有的时候我们并不需要返回一个文档的所有字段,返回部分字段,可以极大的减少数据量的传输,比如,对于customer中的文档,我们只希望返回fristnamelastname两个字段,这个时候就可以使用_source关键字了:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "_source": ["firstname", "lastname"]
}
'

目前为止,我们已经介绍了关于查询的几个关键字,在继续往下进行之前,先来总结一下:

  1. query:指定查询的条件
  2. match_all:匹配所有文档
  3. sort:对结果进行排序
  4. from:查询结果起始位置,从0开始
  5. size:查询结果大小
  6. _source:指定返回的字段

match

match_all查询可以匹配所有的文档,但大部分时候这个查询是没啥意义的,如果只需要查询所有文档,就没有非用ES不可的理由了。当我们需要根据某个字段进行查找,这个时候match就派上用场了,先来看一下用法:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { "match": { "age": 20 } }
}
'

上面的请求会查询年龄为20的文档。除了数字类型,match还可以接受文本和日期类型的查询条件,看下一个例子:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { "match": { "address": "朝阳区和平街" } }
}
'

在这个例子中,我们的查询条件是一个字符串,ES会返回给我们什么样的数据呢?在其他数据库系统中,只有address字段的内容跟查询条件完全一致才能被匹配,而在ES中则大不同。

ES首先会对朝阳区和平街进行分析,假如分析的结果是将这个字符串拆分成了朝阳区和平街,然后,根据倒排索引,会找到所有包含朝阳区或者包含和平街的文档。

如何对查询文本进行分析,并不是一成不变的,我们可以指定分析器,来告诉ES怎么对文本进行拆分,上面的这种拆分需要用到一个中文的分词器叫做ik_smart,ES默认是不支持中文分词的,需要安装第三方的工具。

这里之所以说是或者包含,是因为我们没有指定match的行为,通过operator关键字,我们可以指定是or还是and。比如我们希望找到既包含朝阳区又包含和平街的文档,就可以改写上面的语句如下:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
    "query": {
        "match" : {
            "address" : {
                "query" : "朝阳区和平街",
                "operator" : "and"
            }
        }
    }
}
'

bool

之所以在使用match的时候可以指定operator,是因为match其实是一种布尔类型的查询。在ES中,我们也可以单独的使用这种类型的查询,布尔查询的关键字是boolbool查询将许多个小的查询利用一定的布尔逻辑综合成一个较大的查询。比如,上面查询既包含朝阳区又包含和平街的语句就可以利用bool改写成下面这样:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "朝阳区" } },
        { "match": { "address": "和平街" } }
      ]
    }
  }
}
'

改成bool查询后,逻辑变得更清晰了。同时,我们看到了一个新的关键字must,在bool查询中,并不是用orand来声明逻辑关系的,must表明所有的查询条件都返回True的时候才能匹配,作为对比,用should来表明or的逻辑关系。

should的行为并不像我们通常理解的那样:只要有一个条件返回True就匹配成功。事实上,我们在使用bool的时候,还会涉及到另外一个参数:minimum_should_match,如果不指定这个参数,则默认当所有条件都返回False的时候,也会匹配成功。

除此之外,在一个bool查询中还可以同时指定mustshouldmust_not。还是以上面的例子说明,除了希望address至少包含朝阳区和平街外,还希望age等于20:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "朝阳区" } },
        { "match": { "address": "和平街" } }
      ],
      "must": {"match": {"age": 20}},
      "minimum_should_match": 1   
    }
  }
}
'

term

除了match查询外,还有一个term查询,term查询跟match查询唯一不同的一点是:term查询不会对查询文本进行分析,而是直接去倒排索引中去看都有哪些文档包含要查询的条件;match是首先对要查询的文本进行分析,划分为多个子文本,然后将一个大查询拆分成多个小查询,最后进行汇总处理。因此,如果match查询不能对查询文本进行再划分,那么它与term查询的效果是一样的。

还是上面的查询,将match换成term

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { "term": { "address": "朝阳区和平街" } }
}
'

这个查询的含义就变成了查询在address中包含朝阳区和平街这个字符串的文档。

query与filter

最后,我们来说下filter,对于很多初学者来说,有的时候很难区分query查询和filter查询,尤其是遇到两种方法都能正确得到数据的情况下,更是难决断。所以,在这节我们来看下两者的区别。

首先来说query查询,对于query语句,它要回答的是:某个文档跟查询语句的匹配程度如何?除了决定一个文档是否匹配查询语句外,还要计算一个_score值,这个值就代表了文档的相关性,值越大说明相关性越高。但是,对于这个值,我们大多数时候并不关心,如果不再计算这个值势必会在一定程度上提高ES查询的效率,因此,引入了filter查询。

对于filter语句,只考虑某个文档是否匹配,也就是Yes or No的问题,并不计算相关性,这种情况下可以类比于一般数据库的select语句。filter另外一个跟query不同的地方是,ES会对经常使用的filter进行缓存,以此来提供查询效率,而query不会使用缓存。

如此一来,我们的结论就是,在构造查询语句的时候,能使用filter的地方绝不使用query

我们在上面的内容中介绍了bool查询,它除了支持mustshouldmust_not外,还可以支持filter语句,例如:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "age": {"gte": 20, "lte": 30}
        }
      }
    }
  }
}
'

上面的语句查询了所有年龄在20到30之间的人,它包含一个match_all语句和一个range语句,其中range是放在filter中的,当然如果不使用filter也是可以的,像下面这样:

curl -XGET 'localhost:9200/customer/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": { 
	    "range": {
          "age": {"gte": 20, "lte": 30}
        } 
      }
    }
  }
}
'

不过,还是那句话,能使用filter的地方绝不使用query

到这里,查询语句的介绍就到这里,在入门(3)中,会继续介绍ES中的几种特殊的数据类型:列表类型和嵌套类型。

完!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值