针对ElasticSearch的查询

ElasticSearch提供的API非常丰富,丰富到可以称为空前强大的地步。谁让ElasticSearch的职业就是专门做这个的呢。然而,选择太多,也未必是好事,会让人感到困惑,不知道该在怎样的查询场景运用怎样的查询API。

ES的官方文档就Query DSL而言,就提供了两种查询方式:

  • Leaf query clauses
  • Compound query clauses

后者又包含了多种查询方式:

  • constant_score query
  • boolean query
  • dis_max query
  • function_score query
  • boosting query
  • indices query

坦白说,我一直没有机会去深入探究这些DSL API的适用场景。我在项目上仅仅粗暴地选择了最简单的方式,也就是所谓的Leaf Query Clauses。其实即使是最简单的Leaf Query Clauses,也有很多种语法可以灵活组合呢。而我们的需求是需要从前端提交查询条件,然后交由后台对ES进行查询。这就意味着:我们必须找到一个规则,并确定一个查询条件结构,然后按照ES的Query DSL语法组装满足需求的查询条件。

因此,我只能选择一种Query DSL中最通用的JSON结构,把这一条路走到黑。这个JSON结构可以是递归组合的,基本语法如下示例:

{
   "query": {
        "bool": {
          "must": [
            {
              "bool": {
                "should": [
                  {"term": {"publish_title": "陷入僵局"}},
                  {"match": {"publish_authorname": "金庸"}}
                ]
              }
            },
            {"range": {"ec_score": {"gte": 80872}}}
          ]
        }
      }
    }

ES为查询定义的术语非常另类,至少在我看来确实如此。我们可以将例子中的must与should分别理解为逻辑运算中的and与or。每个must或者should都包含了一个数组,我们可以将其称为条件组,而每个数组元素就可以理解为是一个具体的逻辑条件。这样就可以形成一棵表达式树,如下图所示:


上图中蓝色节点就是一个条件组,绿色节点则代表一个条件。显然,条件组里面可以再包含条件组,当然也可以包含条件。

match、term与range用于特定字段的查询。match相当于like的语义,是一个模糊查询,主要用于Text、Numeric与Date类型的字段;term则相当于=;而range则针对范围了,可以通过gt、gte、lt、lte来设置。

通常情况下,对于numbers、dates、keywords类型,如果希望检索确定的值,采用term查询会更高效。如果是全文本搜索,则应该使用match query。

存储在ElasticSearch中的值是一个JSON结构。在定义Schema时,定义不同type的字段可能会影响查询的语法。在运用如上提到的查询语法时,有两种特殊情况需要考虑到。

一种情况是针对字段为对象,且对象的属性可能不同的时候,ES的mapping定义为:

"detail":{
     "dynamic": true,
     "properties": {}
}

可能的数据如下:

"detail": {
        "author": "",
        "url": "http://www.yidianzixun.com/home?page=article&id=0G3HIZ5r&up=99",
        "keyword": "举报",
        "content": "自己做GPU用来替代intel处理器?"
}

如果要按照detail的content作为查询条件,则查询请求为:

{
    "query": {
        "bool": {
            "must": [{
                "bool": {
                    "should": [{
                        "term": {"detail.content": "intel"}
                    }]
                }
            }]
        }
    }
}

也就是说,这种结果的查询方式与查询第一级字段的方式完全相同,只需要在给定条件表达式中的字段时,用.来体现字段的层级关系即可。

第二种情况是字段包含了一个对象的数组,例如如下定义:

"receivers":{
    "type": "nested",
    "properties":{
        "user_id":{
           "type": "keyword"
          },
       "user_type":{
           "type": "keyword"
       }
    }
}

对应的数据则为:

"receivers": [
      {
          "user_type": "User",
          "user_id": "1001"
      },
      {
          "user_type": "Group",
          "user_id": "2001"
      },
]

要对这样嵌套的结构进行查询,就稍显复杂一些,它对应的是ES中的Nested Query。ES引入了nested节点,通过它可以配置path以及查询条件组。例如我们要查找receivers中,user_id分别为1001与1002,且es_score大于80872的数据,查询条件如下所示:

{
      "query": {
        "bool": {
          "must": [
            {
              "nested": {
                "path": "receivers",
                "query": {
                  "bool": {
                    "should": [
                      {"term": {"receivers.user_id": "10001"}},
                      {"term": {"receivers.user_id": "10002"}}
                    ]
                  }
                }
              }
            },
            {"range": {"ec_score": {"gte": 80872}}}
          ]
        }
      }
    }

nested下配置的path是必须的,它用于指定嵌套对象的路径。ES还允许在nested下设置score_mode,默认为avg,也可以是summinmaxnone。至于query节点则与普通的Leaf query clauses是完全一样的。在我们项目中,编写统一转换的规则时,是将nested节点看做是与should和must相同的节点层次。

2017-04-19 20:07433ElasticSearch