简单查询lite search (字符串查询)是一种有效的命令行ad hoc 查询,但是想要善用搜索,必须使用请求体查询request body search API.之所以这么称呼,是因为大多数的参数以JSON格式所容纳,而不是查询字符串.
请求体查询不但可以处理查询,而且还可以高亮返回结果中的片段.
1.空查询
GET _search
{}
同字符串查询一样,你可以查询一个,或多个索引及类型
GET /index_2014*/type1,type2/_search
{}
也可以使用from, size参数进行分页pagination:
GET /website/_search
{
"from":1,
"size":3
}
注意,from和size数值均可以与实际不符,返回的不过是个空的数组,并不会出错.
那么,这中请求体查询,使用的是携带内容的GET请求方式?
任何一种语言(特别是js)的HTTP库都不允许GET请求中携带交互数据,用户会很惊讶GET请求会允许携带交互数据.
但是真实情况是,一份关于HTTP协议的标准文档RFC中并未定义一个GET请求携带请求体会发生什么!所以,
ES的作者们倾向于使用GET提交查询请求,因为它们觉得这个词相比于POST能更好的描述这种行为.然而,因为携带请求体的GET请求并不被广泛支持,所以search API同样支持POST请求.
POST /website/_search
{
"from":1,
"size":3
}
相比于神秘的字符串查询方式,请求体查询允许我们通过使用query DSL(Domian Specific Language)来写入参数.
2. Query DSL
query DSL是一种灵活的,表现力强的查询语言,ES通过一个简单的JSON接口使用DSL来表现lucene绝大多数的能力.
应当在你的产品中使用这种方式进行查询,它是你的查询更加灵活,精准,易于阅读,且易于debug.
为了使用query DSL,传递一个查询给query参数:
GET /_search
{
"query": YOUR_QUERY_HERE
}
例如,空查询,其实就相当于使用了一个match_all查询子句
POST /website/_search
{
"query": {
"match_all": {}
}
}
match_all是一个查询子句,正如其名字一样,查询所有文档.
查询子句的结构
一个查询子句的典型结构:
{
QUERY_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
如果它是与特定字段有关的:
{
QUERY_NAME: {
FIELD_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
}
例如,你可以使用match查询子句,查询在字段tweet中有elasticsearch的:
查询自己的格式:
{
"match": {
"tweet": "elasticsearch"
}
}
请求:
GET /_search
{
"query": {
"match": {
"tweet": "elasticsearch"
}
}
}
称之为查询子句query clause,代表其都是放到query语句下的.
合并多子句
查询子句像一个简单的积木块一样,可以和其他的子句组合,构成复杂的查询.
子句可以分为:
叶子子句leaf clause,被用作字符串与字段的比较.
复合子句compound clause,备用做合并其他的子句.例如一个bool子句,允许合并其他的子句:must 匹配, must_not,should.它还允许包含non-scoring, filters作为结构化搜索:
{
"bool": {
"must": { "match": { "tweet": "elasticsearch" }},
"must_not": { "match": { "name": "mary" }},
"should": { "match": { "tweet": "full text" }},
"filter": { "range": { "age" : { "gt" : 30 }} }
}
}
非常重要的指出,一个复合查询子句可以包含其他任何查询子句,或者别的复合子句.这意味着复合子句可以被相互嵌套,允许复杂的逻辑表达.
例如,下边的例子,查询邮件,满足:包含business opportunity,同时被标星的邮件;或者同时在folder,indbox,但是没有被标记为spam的邮件.
{
"bool": {
"must": { "match": { "email": "business opportunity" }},
"should": [
{ "match": { "starred": true }},
{ "bool": {
"must": { "match": { "folder": "inbox" }},
"must_not": { "match": { "spam": true }}
}}
],
"minimum_should_match": 1
}
}
不要担心这些例子的细节,我们后续会解释.重点是明白复合语句可以组合多个子句,包括叶子子句或这复合子句到一个简单的查询中.
3. 查询和过滤
ES使用DSL将查询子句放到一个简单的集里,这种简单集合可以被用作两种环境:过滤上下文Filtering context和查询上下文query context.
当被用到过滤环境中,查询query被称作non-scoring or filtering query,这样的查询会这样问问题,'这个文档是否匹配?'答案是二选一,是或否.
例如;
created 的日期范围是否介于2013-2014?
status字段是否包含词published?
las_lon字段的地理位置是否与目标相距不超过10km?
当被使用在查询环境中,查询成为scoring query,它这样问"这个文档的匹配程度如何?"
查询典型的使用:
查找与full text search 最佳匹配的文档
包含单词run,也可能是running,runs,jog, sprint
同时包含quick , brown, fox,它们离得越近,文档的匹配相关性越高.
标记着lucene, search, java,标识词越多,文档的相关性越高.
一个scoring query,计算文档与查询的相关性,并赋值给字段_score,用作依据相关性排序的标准.这种概念同样适用于全文搜索.
注意:
历史上,在ES中,查询和过滤是分开做的,在ES2.0开始,过滤被技术性的消除,同时,查询开始支持non-scoring式的查询.
然而为了区分和简便,我们仍用"过滤"一词来描述non-socring的查询.你可以把filter , filter query , non-scoring query当作一样的.
同样的,如果查询一词被单独的使用,我们就认为是scoring的查询.
性能差异
过滤查询是一个简单的包含与不包含的检查,这是它们计算非常快速.
有各种优化,对于至少有一条过滤查询是很少有文档匹配,同时被频繁的用作non-scoring的查询,可以被放到内存中,更快速获取.
相比之下,scoring查询不但需要查找匹配的文档,并且还要计算相关性,这使得其繁重于non-scoring查询,同时查询的结果是不能够被缓存的.
幸亏有倒排索引,使得一个简单的scoring查询,仅匹配一些文档,性能可以与过滤相比,甚至优于过滤,在跨越数以百万计的文件中.
但是一般情况下,过滤是优于查询的.
过滤的目的是减少文档的数量,这些文档必须被scoring query检查.
什么时候使用?
一般原则,在全文查询,或者需要相关性评分时,使用查询scoring query,其他时候都是使用过滤non-scoring query.
4. 重要的查询语句
ES有很多查询语句,只有少部分经常被使用,我们会在后续的深入查询一章详细学习,现在快速介绍一些重要的语句.
match_all
match_all查询简单的匹配所有文档
{ "match_all": {}}
这个查询经常和过滤器一起使用.
match
match查询是一个标准的查询,无论是查询一个全文文本还是精确值.
如果使用match对全文文本字段进行查询,执行查询之前,先使用针对该字段正确的分析器对查询字符串进行分析.
{ "match": { "tweet": "About Search" }}
如果使用该语句在一个字段上匹配精确值,数值,日期,布尔,以及not_analyzed字符串,
{ "match": { "age": 26 }}
{ "match": { "date": "2014-09-01" }}
{ "match": { "public": true }}
{ "match": { "tag": "full_text" }}
对于确切值搜索,你可能想使用过滤语句,而不是查询,我们很快看到过滤的例子.
相比于字符串查询,match语句查询的语法更加安全.
multi_match
multi_match查询允许在多个字段上进行match一样的查询
{
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
}
range
range查询允许查询数值或日期在一个指定的区间里,该子句接受如下参数:
gt : greater than
gte : greater than or equal to
lt : less than
lte : less than or equal to
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
term
term查询被用作确切值查询,对数值,日期,布尔,not_analyzed确切值字符串
{ "term": { "age": 26 }}
{ "term": { "date": "2014-09-01" }}
{ "term": { "public": true }}
{ "term": { "tag": "full_text" }}
term查询不对输入的文本进行分析,所以它支持确切值查询
terms
terms查询同term查询,但是它允许指定多个匹配值,如果字段包含其中的任何一个,都会返回文档
{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}
exist, missing
exist, missing查询被用作查询指定的字段存在的文档(exist)或者不存在的文档(missing),exist返回存在该字段的文档,missing返回不存在该字段的文档
{
"exists": {
"field": "title"
}
}
5. 组合查询
现实应用中的查询从来都不是简单的,使用多个输入值查询多个字段,依据一系列标准的过滤器.构造一个复杂查询,你需要一种组合多个查询子句在一个搜索请求中的方式.
为了达到这个要求,可以使用bool查询,这个查询接受如下参数:
must: 必须是匹配的文档被包含进来
must_not: 一定是不匹配的文档被包含进来
should: 如果匹配,增加_score,否则没有影响,为每个文档相关性评分.
filter: 必须匹配,是non_scoring的过滤模式,只是简单的包含或不包含.
因为这是我们看到的第一个包含其他查询的查询语句,我们需要谈论相关性评分是怎么计算的.
每个子句分别计算文档的相关性评分,一旦这些结果被计算出来,bool语句将这些分数合并到一起,并且返回一个单个分数值,代表bool操作的总分数.
接下来的查询,寻找文档:title字段匹配查询字符串"how to make millions",并且不被标识为spam.如果文档是starred,或者从2014开始的,它们的排名会比其他的文档高.
{
"bool": {
"must": { "match": { "title": "how to make millions" }},
"must_not": { "match": { "tag": "spam" }},
"should": [
{ "match": { "tag": "starred" }},
{ "range": { "date": { "gte": "2014-01-01" }}}
]
}
}
加上过滤查询:
如果我们不想文档的日期对评分产生影响,我们可以使用filter子句:
{
"bool": {
"must": { "match": { "title": "how to make millions" }},
"must_not": { "match": { "tag": "spam" }},
"should": [
{ "match": { "tag": "starred" }}
],
"filter": {
"range": { "date": { "gte": "2014-01-01" }}
}
}
}
通过将range查询放入filter子句,我们转化它为non-scoring查询,它不再对文档的相关性评分产生影响,并且因为是non-scoring查询,可以使用过滤器的优化来提升性能.
任何一个查询都可以使用这种方式,简单的将查询放到bool语句的filter子句中,会自动转化为non-scoring过滤.
如果需要一个基于多标准的过滤,bool查询本身可以作为non-scoring查询
{
"bool": {
"must": { "match": { "title": "how to make millions" }},
"must_not": { "match": { "tag": "spam" }},
"should": [
{ "match": { "tag": "starred" }}
],
"filter": {
"bool": {
"must": [
{ "range": { "date": { "gte": "2014-01-01" }}},
{ "range": { "price": { "lte": 29.99 }}}
],
"must_not": [
{ "term": { "category": "ebooks" }}
]
}
}
}
}
constant_score查询
尽管不如bool查询经常使用,constant_score查询也依然是有用的,该查询为匹配的文档应用静态的,常数的分数.它主要是在执行过滤查询时使用.
只有过滤子句时,你可以使用该语句代替bool语句.性能是相同的,但是有利于查询的简单性和清晰度
{
"constant_score": {
"filter": {
"term": { "category": "ebooks" }
}
}
}
6. 验证查询
查询可以是非常复杂,尤其是组合了不同的分析器和字段映射的时候,validate-query API可以检查一个请求是否有效.
在请求URL后加/_validate/query
GET /gb/tweet/_validate/query
{
"query": {
"tweet" : {
"match" : "really powerful"
}
}
}
validate请求的响应告诉我们请求是无效的:
{
"valid" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"failed" : 0
}
}
如果要知道问题处在了哪,可以在后边加上参数explain
GET /gb/tweet/_validate/query?explain
{
"query": {
"tweet" : {
"match" : "really powerful"
}
}
}
显然,我们混淆了查询语句的类别和字段的名字
{
"valid" : false,
"_shards" : { ... },
"explanations" : [ {
"index" : "gb",
"valid" : false,
"error" : "org.elasticsearch.index.query.QueryParsingException:
[gb] No query registered for [tweet]"
} ]
}
我们也可以利于expalin参数理解ES是如何解释查询的:
GET /us,gb/_validate/query?explain
{
"query": {
"match": {
"tweet": "really powerful"
}
}
}
为我们查询的每个索引返回一个explanation,因为每个索引有不同的映射和分析器:
{
"valid": true,
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"explanations": [
{
"index": "gb",
"valid": true,
"explanation": "tweet:realli tweet:power"
},
{
"index": "us",
"valid": true,
"explanation": "tweet:really tweet:powerful"
}
]
}
从explanation中,我们可以看出针对tweet字段,match语句是如何将查询字符串really powerful 重写为两个单个词term的.
两个索引的重写词不一样,原因是因为索引gb中的tweet字段使用的是english分析器.