Elasticsearch入门(2)

作者: iszhenyu | 来源:发表于2017-04-11 01:02 被阅读117次

    我们在入门(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中的几种特殊的数据类型:列表类型和嵌套类型。


    这样学机器学习

    相关文章

      网友评论

        本文标题:Elasticsearch入门(2)

        本文链接:https://www.haomeiwen.com/subject/glxyattx.html