跳至主要內容

查询

Mr.Liu大约 13 分钟数据库ElasticsearchNoSQL

请求地址参数查询

接下来我们将使用最简单的形式来演示search API。

索引、类型查询

首先,我们先通过搜索公司的所有员工来演示搜索API的应用。

GET /employee/_search

可以发现,在请求地址中使用了company索引,employee类型,但是并没有指定文档的ID,同时使用的是_search接口。

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "employee",
        "_type" : "_doc",
        "_id" : "rfZt238BJHOwMxbsnp3g",
        "_score" : 1.0,
        "_source" : {
          "firstName" : "Mily",
          "lastName" : "Smith",
          "age" : 22,
          "about" : "I love Python and Java",
          "interests" : [
            "book",
            "music"
          ]
        }
      },
      {
        "_index" : "employee",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "firstName" : "Mily",
          "lastName" : "Lucy",
          "age" : 27,
          "about" : "I love Js",
          "interests" : [
            "sports"
          ]
        }
      },
      {
        "_index" : "employee",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "firstName" : "John2",
          "lastName" : "Smith",
          "age" : 25,
          "about" : "I love Python",
          "interests" : [
            "sports",
            "music"
          ],
          "gender" : "man"
        }
      },
      {
        "_index" : "employee",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "firstName" : "Hilun",
          "lastName" : "Lucy",
          "age" : 25,
          "about" : "I love Js and Java",
          "interests" : [
            "book"
          ]
        }
      },
      {
        "_index" : "employee",
        "_type" : "_doc",
        "_id" : "rvZ_238BJHOwMxbsJZ3F",
        "_score" : 1.0,
        "_source" : {
          "firstName" : "Hilun",
          "lastName" : "Lucy",
          "age" : 25,
          "about" : "I love Js and Java",
          "interests" : [
            "book"
          ]
        }
      }
    ]
  }
}

hits

响应中最重要的部分是hits,它包含了total字段来表示匹配到的文档总数,hits数组还包含了匹配到的前10条数据。

hits数组中的每个结果都包含_index_type和文档的_id字段,被加入到_source字段中这意味着在搜索结果中我们将可以直接使用全部文档。这不像其他搜索引擎只返回文档ID,需要你单独去获取文档。

_score

每个节点都有一个_score字段,这是相关性得分(relevance score),它衡量了文档与查询的匹配程度。默认的,返回的结果中关联性最大的文档排在首位;这意味着,它是按照_score降序排列的。这种情况下,我们没有指定任何查询,所以所有文档的相关性是一样的,因此所有结果的_score都是取得一个中间值1

max_score指的是所有文档匹配查询中_score的最大值。

took

took告诉我们整个搜索请求花费的毫秒数。

shards

_shards节点告诉我们参与查询的分片数(total字段),有多少是成功的(successful字段),有多少的是失败的(failed字段)。通常我们不希望分片失败,不过这个有可能发生。如果我们遭受一些重大的故障导致主分片和复制分片都故障,那这个分片的数据将无法响应给搜索请求。这种情况下,Elasticsearch将报告分片failed,但仍将继续返回剩余分片上的结果。

timeout

time_out值告诉我们查询超时与否。一般的,搜索请求不会超时。如果响应速度比完整的结果更重要,你可以定义timeout参数为10或者10ms(10毫秒),或者1s(1秒)

GET /_search?timeout=10ms

Elasticsearch将返回在请求超时前收集到的结果。

超时不是一个断路器(circuit breaker)(译者注:关于断路器的理解请看警告)。

警告

需要注意的是timeout不会停止执行查询,它仅仅告诉你目前顺利返回结果的节点然后关闭连接。在后台,其他分片可能依旧执行查询,尽管结果已经被发送。

使用超时是因为对于你的业务需求(Service-Level Agreement服务等级协议)来说非常重要,而不是因为你想中断执行长时间运行的查询。

除了搜索特定的索引中特定类型的文档数据之外,我们还可以在请求地址中指定多个索引、多个文档,甚至不指定。

GET /_search

在所有的索引中搜索

GET /employee/_search

在索引employee中搜索

GET /employee,department/_search

在索引baidu和sina的所有类型中搜索

GET /e*,d*/_search

在以b开头或s开头的所有索引中搜索

分页

在上面的搜索中,我们已经得知,每次搜索的文档默认最多只能10条。如果,想要每次的文档数量改变怎么办呢?

Elasticsearch中提供了分页机制,帮助我们实现这个功能。Elasticsearch的分页机制和SQL的LIMIT分页机制十分相似。

  • size:结果数,默认10
  • from:跳过的结果数,默认0

如果你每页显示2个结果,从第一页到第三页请求地址如下:

GET /employee/_search?size=2
GET /employee/_search?size=2&from=2
GET /employee/_search?size=2&from=4

查询字符串

接下来我们要尝试搜索哪些员工的姓氏包含Smith。为了实现这个目的,我们需要使用 查询字符串(Query string)搜索,通过URL来传递查询的关键字:

GET /employee/_search?q=lastName:Smith

我们依旧使用_search接口,然后将参数传入q=。这样我们就可以得到姓Smith的结果。

查询在lastName字段中包含Smith并且在about中包含Python的文档。实际的查询就是:

+lastName:Smith +about:Java

但是在发送请求时,需要对参数进行URL编码,得到一个字符串作为参数:

GET /employee/_search?q=%2BlastName%3ASmith%20%2Babout%3AJava

+前缀表示必须与查询条件匹配。类似地,-前缀表示一定不与查询条件匹配。没有+-的其他条件都是可选的,即匹配的越多,文档就越相关。

请求体参数查询

之前的查询语句,可以帮助我们可以实现一些简单的条件查询。但是如果想要实现复杂的查间,就不建议在请求地址中传参,而是应该使用请求体参数查询。

请求体查询,并不仅仅用来处理查询,而且还可以高亮返回结果中的片段,并且给出帮助你找寻最好结果的相关数据建议。

简单查询

在请求体查询中,我们同样从最基础的_search API开始。

同之前的查询一样,我们可以在请求地址上体现索引和类型,现在只是多了一个请求体参数。

GET /employee/_search
{}

当然,我们也可以使用from和size进行分页查询。

GET /employee/_search
{
  "from": 2,
  "size": 2
}

带请求体参数的GET请求?

任何一种语言的HTTP库都不允许GET请求携带请求体参数。事实上,很多人都很诧异,GET请求中居然会允许携带参数。

实际情况是一份规定HTTP语义及内容的RFC文档-RFC7231,并未规定GET请求中允许携带请求体参数。所以,有些HTTP服务中允许这种行为,而另一些,则不允许这些行为。

Elasticsearch倾向于使用GET提交查询请求,因为在Elasticsearch官方看来,相对于POST,GET更能体现查询这种行为。

然而,因为携带交互数据的GET请求并没有广泛支持,所以search API同样支持POST请求:

POST /employee/_search
{
  "from": 2,
  "size": 2
}

这种情况同样适用于其他携带交互数据的GET API请求。

Query DSL查询

查询字符串是通过命令语句完成点对点的搜索,但这有它的局限性。Elasticsearch提供了更加灵活的查询语言,它被称作Query DSL,通过它我们可以完成更加复杂、强大的搜索任务。

查询与过滤

DSL有自己的一套查询组件,这些组件可以无限组合搭配。这套组件在以下两种情况使用:过滤情况和查询情况。

当使用于过滤情况时,查询被设置成一个不评分或者过滤查询。即这个查间只是简单的问一个问题:这个条件是否匹配?回答也是十分简洁,yes或者no,二者必居其一。

  • created 时间是否在2013与2014这个区间?
  • status 字段是否包含 pulished 这个单词?

当使用查询情况时,查询就变成了一个评分查询。和不评分的查询类似,也要去判断这个文档是否匹配,同时还要判断这个文档匹配的有多好(匹配度如何)。此查询的典型用法是用于查找以下文档:

  • 查找与ful1 text search这个词语最匹配的文档

  • 包含run这个词,也能匹配runs,running

  • 包含quick、brow、 fox这几个词,词之间的距离越近,文档相关性越高

  • 标签 lucene, search、 java,标签越多,相关性越高

一个评分查询计算每一个文档与此查询的相关度,同时将这个相关度分配给表示相关性的字段_socre,并且按照相关性对匹配到的文档进行排序。这种相关性的概念是非常适合全文搜索的情况,因为全文搜索几乎没有完全正确的答案。

在Elasticsearch2.0之后,过滤(filter)已经从技术被排除了,但所有的查询(query)可以变成不评分查询。

过滤查询只是简单的检查包含或者排除,这使得计算非常快。并且计算结果经常会被缓存到内存中。 相反,评分查询不仅仅要找出匹配的文档,还要计算每个文档的匹配性。计算匹配性比不评分查询费力得多。并且,查询结果并不缓存。

通常的规则是,使用查询语句来进行全文搜索或其他影响相关性得分的搜索。其他情况都使用过滤。

查询语句

DSL(Domain Specific Language 领域特定语言)需要使用json作为主体:

GET /_index/_search
{
  "query": "查询语句"
}

注意:所有的查询语句都是json字符串

一个查询语句一般使用这种结构:

{
    "QUERY_NAME": {
        "ARGUMENT": "VALUE"
    }
}

或者指向一个指定的字段:

{
    "QUERY_NAME": {
        "FIELD_NAME":{
            "ARGUMENT": "VALUE"
        }
    }
}

match_all

match_all在功能等同于空查询,正如其名字一样,匹配所有的文档。在没有指定查询方式时,它是默认的查询:

GET /employee/_search
{
  "query": {
    "match_all": {}
  }
}

match

无论在任何字段上进行的是全文搜索,还是精确查询,match查询都是可用的标准查询。 如果在一个全文字段上使用match,在查询前,它将用正确的分析器去分析查询字符串:

GET /employee/_search
{
  "query": {
    "match": {
      "about": "Python"
    }
  }
}

如果在一个精确的字段(数字、日期、布尔或者一个not_analyzed字符申字段)上使用它,那么它将会精确地匹配给定的值。

{"match": {"age": 26}}
{"match": {"date": "2014-09-01"}}
{"match": {"public": true}}
{"match": {"tag": "python"}}

对于精确值的查词,建议便用filter语句来取代query,因为filter会被缓存。

multi_match

可以在多个字段上执行相同的match查询

GET /employee/_search
{
  "query": {
    "multi_match": {
      "query": "book",
      "fields": [
        "about",
        "interests"
      ]
    }
  }
}

range

查询找出那些落在指定区间内的数字或时间

GET /employee/_search

{
  "query": {
    "range": {
      "age": {
        "gt": 20,
        "lt": 25
      }
    }
  }
}

被允许的操作符:

gt:大于

gte:大于等于

lt:小于

lte:小于等于

term

被用于精确值匹配,这些值可以是数字、日期、布尔或者一个not_analyzed字符申字段

{"term": {"age": 26}}
{"term": {"date": "2014-09-01"}}
{"term": {"public": true}}
{"term":{"about":"python"}}

terms

terms查询和term查询一样,但它允许指定多个值进行匹配。如果这个字段包含了指定值的任何一个,那么这个文档满足条件:

GET /employee/_search
{
  "query": {
    "terms": {
      "interests": [
        "music",
        "book"
      ]
    }
  }
}

exists 和 missing

exists 和 missing 被用来查询指定字段中有值(exists)和无值(missing)的文档。这类似SQL中的IS NULL(missing)和IS NOT NULL (exists)。

GET /employee/_search

{
  "query": {
    "exists": {
      "field": "about"
    }
  }
}

这些查询经常被用来判断某个字段是否有值。

Filter Query Missing 已经从 ES 5 版本移除,针对 ES 5.2.2 以上版本,查询不包含某字段(无 about 字段)的数据,DSL 如下:

GET /employee/_search
{
  "query": {
    "bool": {
      "must_not": {
        "exists": {
          "field": "about"
        }
      }
    }
  }
}

组合查询语句

bool

现实的查询需求从来都没有那么简单:它们需要在多个字段上查间多种多样的文本,并且根据一系列的标准来过滤。

为了构建高级查询,可以使用bool查询将多查询语句组合在一起,成为用户自己想要的布尔查间它可以接受以下参数:

must:必须匹配这些条件 must_not:必须不匹配这些条件 should:如果满足这些条件,将增加_score,否则,无任何影响。主要用于修正每个文档的相关性得分。 filter:必须匹配,但它以不评分、过滤模式进行匹配。这些语句不影响评分,只是根据过滤标准来排除或包含文档。

下面的用于查找 lastName字段匹配Smith并且interests不为sports的员工。那些interests为music或者age大于等于22的员工,将比其他员工有更高的排名。如果两者都满足,那么排名更高:

GET /employee/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "lastName": "Smith"
        }
      },
      "must_not": {
        "match": {
          "interests": "sports"
        }
      },
      "should": [
        {
          "match": {
            "interests": "music"
          }
        },
        {
          "range": {
            "age": {
              "gte": 22
            }
          }
        }
      ]
    }
  }
}

如果没有must语句,那么至少需要能够匹配其中的一条should语句。但如果至少存在一条must语句,则should语句就无所谓。

如果前面的查询不想因为年龄而影响得分,可以用filter语句重写:

GET /employee/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "lastName": "Smith"
        }
      },
      "must_not": {
        "match": {
          "interests": "sports"
        }
      },
      "should": [
        {
          "match": {
            "interests": "music"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gte": 22
          }
        }
      }
    }
  }
}

通过将range查询移到filter语句中,则该条件将不再影响文档的相关性排名。 所有查询都可以借鉴这种方式,将查询移到bool查询的filter语句中,这样它就自动转成一个不评分的filter。 如果需要通过多个不同的标准来过滤文档,bool查询本身也可以用作不评分的查询。

GET /employee/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "interests": "music"
        }
      }
    }
  }
}

const_score

将一个不变的常量评分应用于匹配的文档。它被用于只需要执行一个filter而没有其他查询的情况。

以便用它来取代只有filter语句的bool查询。在性能上是完全相同的,但对于提高查间简洁性和清晰度有很多帮助。

GET /employee/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "interests": "music"
        }
      },
      "boost": 1.5
    }
  }
}

验证查询

查询有时候会非常复杂,不过可以通过validate-query API来验证查询是否合法。

GET /employee/_validate/query
{
  "query": {
    "constant_score": {
      "must": {
        "match": {
          "interests": "music"
        }
      }
    }
  }
}

以上validate请求的响应可以告诉我们这个查询是否合法。

{
    "valid": false
}

为了找出查询不合法的原因,可以将explain参数添加到查询字符串中:

GET /employee/_validate/query?explain
{
  "query": {
    "constant_score": {
      "must": {
        "match": {
          "interests": "music"
        }
      }
    }
  }
}

explain参数可以通过更多关于查询不合法的信息。

{
  "valid" : false,
  "error" : "org.elasticsearch.common.ParsingException: [constant_score] query does not support [must]"
}