Elasticsearch入门

来自[https://www.elastic.co/guide/cn/elasticsearch/guide/current/_retrieving_a_document.html]

面向文档

应用中的对象很少只是简单的键值列表,更多时候它拥有复杂的数据结构,比如包含日期、地理位置、另一个对象或者数组。
总有一天你会想到把这些对象存储到数据库中。将这些数据保存到由行和列组成的关系数据库中,就好像是把一个丰富,信息表现力强的对象拆散了放入一个非常大的表格中:你不得不拆散对象以适应表模式(通常一列表示一个字段),然后又不得不在查询的时候重建它们。
Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。这种理解数据的方式与以往完全不同,这也是Elasticsearch能够执行复杂的全文搜索的原因之一。

Json

Elasticsearch使用JSON作为文档序列化格式
例如使用JSON文档来表示一个用户对象

{
    "email":      "john@smith.com",
    "first_name": "John",
    "last_name":  "Smith",
    "info": {
        "bio":         "Eco-warrior and defender of the weak",
        "age":         25,
        "interests": [ "dolphins", "whales" ]
    },
    "join_date": "2014/05/01"
}

例子

我们现在开始进行一个简单教程,它涵盖了一些基本的概念介绍,比如索引(indexing)、搜索(search)以及聚合(aggregations)。通过这个教程,我们可以让你对Elasticsearch能做的事以及其易用程度有一个大致的感觉。
我们接下来将陆续介绍一些术语和基本的概念,但就算你没有马上完全理解也没有关系。我们将在本书的各个章节中更加深入的探讨这些内容。
所以,坐下来,开始以旋风般的速度来感受Elasticsearch的能力吧!

让我们建立一个员工目录

假设我们刚好在Megacorp工作,这时人力资源部门出于某种目的需要让我们创建一个员工目录,这个目录用于促进人文关怀和用于实时协同工作,所以它有以下不同的需求:
1.数据能够包含多个值的标签、数字和纯文本。
2.检索任何员工的所有信息。
3.支持结构化搜索,例如查找30岁以上的员工。
4.支持简单的全文搜索和更复杂的短语(phrase)搜索
5.高亮搜索结果中的关键字
6.能够利用图表管理分析这些数据

索引员工文档

首先是存储员工数据,每个文档代表一个员工,在Elasticsearch中存储数据的行为叫做索引。
在Elasticsearch中,文档属于一种类型(type),而这些类型存在于索引(index)中
我们可以画一些简单的对比图来类比传统关系型数据库:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
Elasticsearch 集群可以包含多个索引(indices),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。

所以为了创建员工目录,我们将进行如下操作:
为每个员工的文档(document)建立索引,每个文档包含了相应员工的所有信息。
每个文档的类型为employee。
employee类型归属于索引megacorp。
megacorp索引存储在Elasticsearch集群中。
实际上这些都是很容易的(尽管看起来有许多步骤)。我们能通过一个命令执行完成的操作:

PUT /megacorp/employee/1
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

我们看到path:/megacorp/employee/1包含三部分信息:
名字 说明
megacorp 索引名
employee 类型名
1 这个员工的ID
很简单吧!它不需要你做额外的管理操作,比如创建索引或者定义每个字段的数据类型。我们能够直接索引文档,Elasticsearch已经内置所有的缺省设置,所有管理操作都是透明的。
接下来,让我们在目录中加入更多员工信息:

PUT /megacorp/employee/2
{
    "first_name" :  "Jane",
    "last_name" :   "Smith",
    "age" :         32,
    "about" :       "I like to collect rock albums",
    "interests":  [ "music" ]
}

PUT /megacorp/employee/3
{
    "first_name" :  "Douglas",
    "last_name" :   "Fir",
    "age" :         35,
    "about":        "I like to build cabinets",
    "interests":  [ "forestry" ]
}

检索文档

现在Elasticsearch中已经存储了一些数据,我们可以根据业务需求开始工作了。第一个需求是能够检索单个员工的信息。
这对于Elasticsearch来说非常简单。我们只要执行HTTP GET请求并指出文档的“地址”——索引、类型和ID既可。根据这三部分信息,我们就可以返回原始JSON文档:

GET /megacorp/employee/1
响应的内容中包含一些文档的元信息,John Smith的原始JSON文档包含在_source字段中。
{
  "_index" :   "megacorp",
  "_type" :    "employee",
  "_id" :      "1",
  "_version" : 1,
  "found" :    true,
  "_source" :  {
      "first_name" :  "John",
      "last_name" :   "Smith",
      "age" :         25,
      "about" :       "I love to go rock climbing",
      "interests":  [ "sports", "music" ]
  }
}

我们通过HTTP方法GET来检索文档,同样的,我们可以使用DELETE方法删除文档,使用HEAD方法检查某文档是否存在。如果想更新已存在的文档,我们只需再PUT一次。

GET请求非常简单——你能轻松获取你想要的文档。让我们来进一步尝试一些东西,比如简单的搜索!
我们尝试一个最简单的搜索全部员工的请求:
GET /megacorp/employee/_search
你可以看到我们依然使用megacorp索引和employee类型,但是我们在结尾使用关键字_search来取代原来的文档ID。响应内容的hits数组中包含了我们所有的三个文档。默认情况下搜索会返回前10个结果。

{
   "took":      6,
   "timed_out": false,
   "_shards": { ... },
   "hits": {
      "total":      3,
      "max_score":  1,
      "hits": [
         {
            "_index":         "megacorp",
            "_type":          "employee",
            "_id":            "3",
            "_score":         1,
            "_source": {
               "first_name":  "Douglas",
               "last_name":   "Fir",
               "age":         35,
               "about":       "I like to build cabinets",
               "interests": [ "forestry" ]
            }
         },
         {
            "_index":         "megacorp",
            "_type":          "employee",
            "_id":            "1",
            "_score":         1,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         },
         {
            "_index":         "megacorp",
            "_type":          "employee",
            "_id":            "2",
            "_score":         1,
            "_source": {
               "first_name":  "Jane",
               "last_name":   "Smith",
               "age":         32,
               "about":       "I like to collect rock albums",
               "interests": [ "music" ]
            }
         }
      ]
   }
}

查询字符串(query string)搜索

接下来,让我们搜索姓氏中包含“Smith”的员工。要做到这一点,我们将在命令行中使用轻量级的搜索方法。这种方法常被称作查询字符串(query string)搜索,因为我们像传递URL参数一样去传递查询语句:
GET /megacorp/employee/_search?q=last_name:Smith
我们在请求中依旧使用_search关键字,然后将查询语句传递给参数q=。这样就可以得到所有姓氏为Smith的结果:

{
   ...
   "hits": {
      "total":      2,
      "max_score":  0.30685282,
      "hits": [
         {
            ...
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         },
         {
            ...
            "_source": {
               "first_name":  "Jane",
               "last_name":   "Smith",
               "age":         32,
               "about":       "I like to collect rock albums",
               "interests": [ "music" ]
            }
         }
      ]
   }
}

DSL查询

DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。我们可以这样表示之前关于“Smith”的查询:

GET /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "last_name" : "Smith"
        }
    }
}

这会返回与之前查询相同的结果。你可以看到有些东西改变了,我们不再使用查询字符串(query string)做为参数,而是使用请求体代替。这个请求体使用JSON表示,其中使用了match语句(查询类型之一,具体我们以后会学到)。

我们让搜索稍微再变的复杂一些。我们依旧想要找到姓氏为“Smith”的员工,但是我们只想得到年龄大于30岁的员工。我们的语句将添加过滤器(filter),它使得我们高效率的执行一个结构化搜索:

GET /megacorp/employee/_search
{
    "query" : {
        "filtered" : {
            "filter" : {
                "range" : {
                    "age" : { "gt" : 30 } <1>
                }
            },
            "query" : {
                "match" : {
                    "last_name" : "smith" <2>
                }
            }
        }
    }
}
<1> 这部分查询属于区间过滤器(range filter),它用于查找所有年龄大于30岁的数据——gt为"greater than"的缩写。
<2> 这部分查询与之前的match语句(query)一致。
全文搜索

到目前为止搜索都很简单:搜索特定的名字,通过年龄筛选。让我们尝试一种更高级的搜索,全文搜索——一种传统数据库很难实现的功能。
我们将会搜索所有喜欢“rock climbing”的员工:

GET /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}
你可以看到我们使用了之前的match查询,从about字段中搜索"rock climbing",我们得到了两个匹配文档:
{
   ...
   "hits": {
      "total":      2,
      "max_score":  0.16273327,
      "hits": [
         {
            ...
            "_score":         0.16273327, <1>
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         },
         {
            ...
            "_score":         0.016878016, <2>
            "_source": {
               "first_name":  "Jane",
               "last_name":   "Smith",
               "age":         32,
               "about":       "I like to collect rock albums",
               "interests": [ "music" ]
            }
         }
      ]
   }
}
<1><2> 结果相关性评分。

默认情况下,Elasticsearch根据结果相关性评分来对结果集进行排序,所谓的「结果相关性评分」就是文档与查询条件的匹配程度。很显然,排名第一的John Smith的about字段明确的写到“rock climbing”。
但是为什么Jane Smith也会出现在结果里呢?原因是“rock”在她的abuot字段中被提及了。因为只有“rock”被提及而“climbing”没有,所以她的_score要低于John。
这个例子很好的解释了Elasticsearch如何在各种文本字段中进行全文搜索,并且返回相关性最大的结果集。相关性(relevance)的概念在Elasticsearch中非常重要,而这个概念在传统关系型数据库中是不可想象的,因为传统数据库对记录的查询只有匹配或者不匹配。

短语搜索

目前我们可以在字段中搜索单独的一个词,这挺好的,但是有时候你想要确切的匹配若干个单词或者短语(phrases)。例如我们想要查询同时包含"rock"和"climbing"(并且是相邻的)的员工记录。
要做到这个,我们只要将match查询变更为match_phrase查询即可:

GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    }
}
毫无疑问,该查询返回John Smith的文档:
{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         }
      ]
   }
}

高亮搜索

很多应用喜欢从每个搜索结果中高亮(highlight)匹配到的关键字,这样用户可以知道为什么这些文档和查询相匹配。在Elasticsearch中高亮片段是非常容易的。
让我们在之前的语句上增加highlight参数:

GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    },
    "highlight": {
        "fields" : {
            "about" : {}
        }
    }
}

当我们运行这个语句时,会命中与之前相同的结果,但是在返回结果中会有一个新的部分叫做highlight,这里包含了来自about字段中的文本,并且用<em></em>来标识匹配到的单词。

{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            },
            "highlight": {
               "about": [
                  "I love to go <em>rock</em> <em>climbing</em>" <1>
               ]
            }
         }
      ]
   }
}
<1> 原有文本中高亮的片段

分析

最后,我们还有一个需求需要完成:允许管理者在职员目录中进行一些分析。 Elasticsearch有一个功能叫做聚合(aggregations),它允许你在数据上生成复杂的分析统计。它很像SQL中的GROUP BY但是功能更强大。
举个例子,让我们找到所有职员中最大的共同点(兴趣爱好)是什么:

GET /megacorp/employee/_search
{
  "aggs": {
    "all_interests": {
      "terms": { "field": "interests" }
    }
  }
}
暂时先忽略语法只看查询结果:
{
   ...
   "hits": { ... },
   "aggregations": {
      "all_interests": {
         "buckets": [
            {
               "key":       "music",
               "doc_count": 2
            },
            {
               "key":       "forestry",
               "doc_count": 1
            },
            {
               "key":       "sports",
               "doc_count": 1
            }
         ]
      }
   }
}

聚合

最后,我们还有一个需求需要完成:允许管理者在职员目录中进行一些分析。 Elasticsearch有一个功能叫做聚合(aggregations),它允许你在数据上生成复杂的分析统计。它很像SQL中的GROUP BY但是功能更强大。
举个例子,让我们找到所有职员中最大的共同点(兴趣爱好)是什么:

GET /megacorp/employee/_search
{
  "aggs": {
    "all_interests": {
      "terms": { "field": "interests" }
    }
  }
}
暂时先忽略语法只看查询结果:
{
   ...
   "hits": { ... },
   "aggregations": {
      "all_interests": {
         "buckets": [
            {
               "key":       "music",
               "doc_count": 2
            },
            {
               "key":       "forestry",
               "doc_count": 1
            },
            {
               "key":       "sports",
               "doc_count": 1
            }
         ]
      }
   }
}
我们可以看到两个职员对音乐有兴趣,一个喜欢林学,一个喜欢运动。这些数据并没有被预先计算好,它们是实时的从匹配查询语句的文档中动态计算生成的。如果我们想知道所有姓"Smith"的人最大的共同点(兴趣爱好),我们只需要增加合适的语句既可:
GET /megacorp/employee/_search
{
  "query": {
    "match": {
      "last_name": "smith"
    }
  },
  "aggs": {
    "all_interests": {
      "terms": {
        "field": "interests"
      }
    }
  }
}
all_interests聚合已经变成只包含和查询语句相匹配的文档了:
 ...
  "all_interests": {
     "buckets": [
        {
           "key": "music",
           "doc_count": 2
        },
        {
           "key": "sports",
           "doc_count": 1
        }
     ]
  }
聚合也允许分级汇总。例如,让我们统计每种兴趣下职员的平均年龄:
GET /megacorp/employee/_search
{
    "aggs" : {
        "all_interests" : {
            "terms" : { "field" : "interests" },
            "aggs" : {
                "avg_age" : {
                    "avg" : { "field" : "age" }
                }
            }
        }
    }
}
虽然这次返回的聚合结果有些复杂,但任然很容易理解:
  ...
  "all_interests": {
     "buckets": [
        {
           "key": "music",
           "doc_count": 2,
           "avg_age": {
              "value": 28.5
           }
        },
        {
           "key": "forestry",
           "doc_count": 1,
           "avg_age": {
              "value": 35
           }
        },
        {
           "key": "sports",
           "doc_count": 1,
           "avg_age": {
              "value": 25
           }
        }
     ]
  }
该聚合结果比之前的聚合结果要更加丰富。我们依然得到了兴趣以及数量(指具有该兴趣的员工人数)的列表,但是现在每个兴趣额外拥有avg_age字段来显示具有该兴趣员工的平均年龄。
即使你还不理解语法,但你也可以大概感觉到通过这个特性可以完成相当复杂的聚合工作,你可以处理任何类型的数据。

Elasticsearch的分布式特性
Elasticsearch是为分布式而生的,并且它的设计隐藏了分布式本身的复杂性。
Elasticsearch致力于隐藏分布式系统的复杂性。以下这些操作都是在底层自动完成的:
将你的文档分区到不同的容器或者分片(shards)中,它们可以存在于一个或多个节点中。
将分片均匀的分配到各个节点,对索引和搜索做负载均衡。
冗余每一个分片,防止硬件故障造成的数据丢失。
将集群中任意一个节点上的请求路由到相应数据所在的节点。
无论是增加节点,还是移除节点,分片都可以做到无缝的扩展和迁移。

「索引」含义的区分

你可能已经注意到索引(index)这个词在Elasticsearch中有着不同的含义,所以有必要在此做一下区分:
索引(名词) 如上文所述,一个索引(index)就像是传统关系数据库中的数据库,它是相关文档存储的地方,index的复数是indices 或indexes。
索引(动词) 「索引一个文档」表示把一个文档存储到索引(名词)里,以便它可以被检索或者查询。这很像SQL中的INSERT关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。
倒排索引 传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用一种叫做倒排索引(inverted index)的数据结构来达到相同目的。

查询语言DSL:
下面的命令,可以搜索全部的文档

{
  "query":{"match_all": {}}
}

query定义了查询,match_all声明了查询的类型,还有其他的参数可以控制返回的结果。

{
  "query":{"match_all":{}},
    "size":1
}
上面的命令返回了所有文档数据中的第一条文档。如果size不指定,默认是10
下面的命令指定了文档返回的排序方式
{
    "query":{"match_all":{}},
      "sort":{"balance":{"order":"desc"}}
}

执行搜索,下面深入了解一些常用的DSL
返回文档的所有内容,网络的开销非常大,下面的例子指定了返回特定的字段。

{
    "query":{"match_all":{}},
      "_source":["account_number","balance"]
}

查询特定字段的特定内容

{
    "query":{"match":{"account_number":20}}
}

查询地址为mill或者lane的信息

{
    "query":{"match":{"address":"mill lane"}}
}

如果想返回同时包含mill和lane的地址,可以通过match_phrase查询

{
    "query":{"match_phrase":{"address":"mill lane"}}
}

es还提供了bool查询,它可以将很多个小的查询组成一个更为复杂的查询
例如查询同时包含mill和lane的文档

{
    "query":{
        "bool":{
            “must”:[
                {“match”:{"address":"mill"}},
                {"match":{"address":"lane"}}
            ]
        }
      }
}

修改bool参数,可以将查询改为包含mill或者lane的文档

{
    "query":{
        "bool":{
            “should”:[
                {“match”:{"address":"mill"}},
                {"match":{"address":"lane"}}
            ]
        }
      }
}

也可以改为must_not,排除包含mill和lane的文档

{
    "query":{
        "bool":{
            “must_not”:[
                {“match”:{"address":"mill"}},
                {"match":{"address":"lane"}}
            ]
        }
      }
}

bool查询可以同时使用must、should、must_not组成一个复杂的查询

{
    "query":{
        "bool":{
            "must":[
                {"match":{"age":"40"}}
              ],
            “must_not”:[
                {“match”:{"address":"mill"}},
                {"match":{"address":"lane"}}
              ]
        }
      }
}

过滤查询
返回的结果中score字段指定了文档的分数,使用查询会计算文档的分数,最后通过分数来确定哪些文档更相关。
有时候我们可能不需要分数,这时我们就可以使用filter进行过滤,它不会去计算分值,因此效率也就会高一些。
filter过滤可以嵌套在bool查询内部使用,例如

{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}
搜索

很多搜索是开箱即用的(不理解),为了充分了解搜索,需要理解下面的三个概念:
1.映射
2.分析
3.领域特定查询语言(DSL)
#######空搜索
搜索API的最基础的形式是没有指定任何查询的空搜索,它简单地返回集群中所有索引下的所有文档:
GET /_search
返回的结果如下:
{
"hits" : {
"total" : 14,
"hits" : [
{
"_index": "us",
"_type": "tweet",
"_id": "7",
"_score": 1,
"_source": {
"date": "2014-09-17",
"name": "John Smith",
"tweet": "The Query DSL is really powerful and flexible",
"user_id": 2
}
},
... 9 RESULTS REMOVED ...
],
"max_score" : 1
},
"took" : 4,
"_shards" : {
"failed" : 0,
"successful" : 10,
"total" : 10
},
"timed_out" : false
}

hits

返回结果中最重要的部分,它包含的total字段表示匹配到的文档总数,并且hits数组包含了查询结果的前十个文档。
在 hits 数组中每个结果包含文档的 _index 、 _type 、 _id ,加上 _source 字段。这意味着我们可以直接从返回的搜索结果中使用整个文档。这不像其他的搜索引擎,仅仅返回文档的ID,需要你单独去获取文档。

每个结果还有一个 _score 它衡量了文档与查询的匹配程度。默认情况下,首先返回最相关的文档结果,就是说,返回的文档是按照 _score 降序排列的。在这个例子中,我们没有指定任何查询,故所有的文档具有相同的相关性,因此对所有的结果而言 1 是中性的 _score

max_score 值是与查询所匹配文档的 _score 的最大值。

took 值告诉我们执行整个搜索请求耗费了多少毫秒

_shards以及这些分片成功了多少个失败了多少个。正常情况下我们不希望分片失败,但是分片失败是可能发生的。如果我们遭遇到一种灾难级别的故障,在这个故障中丢失了相同分片的原始数据和副本,那么对这个分片将没有可用副本来对搜索请求作出响应。假若这样,Elasticsearch 将报告这个分片是失败的,但是会继续返回剩余分片的结果。

timed_out值告诉我们查询是否超时。默认情况下,搜索请求不会超时。如果低响应时间比完成结果更重要,你可以指定 timeout 为 10 或者 10ms(10毫秒),或者 1s(1秒):
在请求超时之前,Elasticsearch 将会返回已经成功从每个分片获取的结果。
应当注意的是 timeout 不是停止执行查询,它仅仅是告知正在协调的节点返回到目前为止收集的结果并且关闭连接。在后台,其他的分片可能仍在执行查询即使是结果已经被发送了。
使用超时是因为 SLA(服务等级协议)对你是很重要的,而不是因为想去中止长时间运行的查询。

多索引 多类型

如果不对某一特殊的索引或者类型做限制,就会搜索集群中的所有文档。Elasticsearch 转发搜索请求到每一个主分片或者副本分片,汇集查询出的前10个结果,并且返回给我们。

然而,经常的情况下,你想在一个或多个特殊的索引并且在一个或者多个特殊的类型中进行搜索。我们可以通过在URL中指定特殊的索引和类型达到这种效果,如下所示:
/_search
在所有的索引中搜索所有的类型
/gb/_search
gb 索引中搜索所有的类型
/gb,us/_search
gbus 索引中搜索所有的文档
/g,u/_search
在任何以 g 或者 u 开头的索引中搜索所有的类型
/gb/user/_search
gb 索引中搜索 user 类型
/gb,us/user,tweet/_search
gbus 索引中搜索 usertweet 类型
/_all/user,tweet/_search
在所有的索引中搜索 usertweet 类型
搜索一个索引有五个主分片和搜索五个索引各有一个分片准确来所说是等价的。

分页

在之前的 [空搜索]中说明了集群中有14 个文档匹配了(empty)query 。 但是在 hits 数组中只有 10 个文档。如何才能看到其他的文档?
和 SQL 使用 LIMIT 关键字返回单个 page 结果的方法相同,Elasticsearch 接受 fromsize 参数:
size
显示应该返回的结果数量,默认是 10
from
显示应该跳过的初始结果数量,默认是 0
如果每页展示 5 条结果,可以用下面方式请求得到 1 到 3 页的结果:
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
考虑到分页过深以及一次请求太多结果的情况,结果集在返回之前先进行排序。 但请记住一个请求经常跨越多个分片,每个分片都产生自己的排序结果,这些结果需要进行集中排序以保证整体顺序是正确的。
在分布式系统中深度分页
理解为什么深度分页是有问题的,我们可以假设在一个有 5 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。

现在假设我们请求第 1000 页--结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。

可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。这就是 web 搜索引擎对任何查询都不要返回超过 1000 个结果的原因。
#######轻量搜索
有两种形式的搜索API,一种是“轻量”的查询字符串版本,需要在查询字符串中传递所有的参数,另一种是请求体方式,需要使用JSON格式和更加丰富的查询表达式作为搜索语言。
1.查询字符串
例如,查询在 tweet 类型中 tweet 字段包含 elasticsearch 单词的所有文档:
GET /_all/tweet/_search?q=tweet:elasticsearch

下一个查询在 name 字段中包含 john 并且在 tweet 字段中包含 mary 的文档。实际的查询就是这样
+name:john +tweet:mary
但是查询字符串参数所需要的 百分比编码 (译者注:URL编码)实际上更加难懂:
GET /_search?q=%2Bname%3Ajohn+%2Btweet%3Amary

  • 前缀表示必须与查询条件匹配。类似地, - 前缀表示一定不与查询条件匹配。没有 + 或者 - 的所有其他条件都是可选的——匹配的越多,文档就越相关。
    #######映射和分析
    #######精确值和全文
    全文是指文本数据。
倒排索引

一个倒排索引由文档中所有不重复词的列表够成,对于其中每一个词,有一个包含它的文档列表。
#######分析与分析器
分析的过程包括:
1.将一块文本分成适合于倒排索引的独立的词条。
2.将这些词条统一化为标准格式以提高他们的“可搜索性”。
分析器:
分析器会执行上面的分析过程。它其实是将三个功能封装到一个包里面,
(1)字符过滤器
字符串会按顺序通过每个字符过滤器,它们的作用是在分词整理字符串之前,可以将字符串中的html去掉,或者将&转化为‘and’。
(2)分词器
字符串会被分词器分为单个的词条。
(3)Token过滤器
最后,词条会按顺序通过每个token过滤器,这个过程中可能会改变词条(例如将大写变成小写,删除词条、增加词条等)。
ElasticSearch提供了开箱即用的字符过滤器、分词器和token过滤器,这些可以组合起来形成自定义的分析器来用于不同的目的。
1.当查询一个全文域时,会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。
2.当查询一个精确值域时,不会分析查询字符串,而是搜索你指定的精确值。
现在你可以理解在 [开始章节] 的查询为什么返回那样的结果:

  • date 域包含一个精确值:单独的词条 2014-09-15
  • _all 域是一个全文域,所以分词进程将日期转化为三个词条: 201409, 和 15
当我们在 `_all` 域查询 `2014`,它匹配所有的12条推文,因为它们都含有 `2014` :
GET /_search?q=2014              # 12 results
当我们在 _all 域查询 2014-09-15`,它首先分析查询字符串,产生匹配 `2014`, `09`, 或 `15 中 任意 词条的查询。这也会匹配所有12条推文,因为它们都含有 2014 :
GET /_search?q=2014-09-15        # 12 results !
当我们在 date 域查询 `2014-09-15`,它寻找 精确 日期,只找到一个推文:
GET /_search?q=date:2014-09-15   # 1  result
当我们在 date 域查询 `2014`,它找不到任何文档,因为没有文档含有这个精确日志:
GET /_search?q=date:2014         # 0  results !

测试分析器
有些时候很难理解分词的过程和实际被存储到索引中的词条,特别是你刚接触Elasticsearch。为了理解发生了什么,你可以使用 analyze API 来看文本是如何被分析的。在消息体里,指定分析器和要分析的文本:

GET /_analyze
{
  "analyzer": "standard",
  "text": "Text to analyze"
}

结果中每个元素代表一个单独的词条:

{
   "tokens": [
      {
         "token":        "text",
         "start_offset": 0,
         "end_offset":   4,
         "type":         "<ALPHANUM>",
         "position":     1
      },
      {
         "token":        "to",
         "start_offset": 5,
         "end_offset":   7,
         "type":         "<ALPHANUM>",
         "position":     2
      },
      {
         "token":        "analyze",
         "start_offset": 8,
         "end_offset":   15,
         "type":         "<ALPHANUM>",
         "position":     3
      }
   ]
}

token 是实际存储到索引中的词条。 position 指明词条在原始文本中出现的位置。 start_offset 和 end_offset 指明字符在原始字符串中的位置。
指定分析器
当Elasticsearch在你的文档中检测到一个新的字符串域,它会自动设置其为一个全文 字符串 域,使用 标准 分析器对它进行分析。
你不希望总是这样。可能你想使用一个不同的分析器,适用于你的数据使用的语言。有时候你想要一个字符串域就是一个字符串域--不使用分析,直接索引你传入的精确值,例如用户ID或者一个内部的状态域或标签。
要做到这一点,我们必须手动指定这些域的映射。
#######映射
为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或精确值字符串, Elasticsearch 需要知道每个域中数据的类型。这个信息包含在映射中。
ElasticSearch支持如下的简单域类型:
1.字符串:string
2.整数:byte,short,integer,long
3.浮点数:float,double
4.布尔型:boolean
5.日期:date
当你索引一个包含新域的文档--之前未曾出现-- Elasticsearch会使用 动态映射,通过JSON中基本数据类型,尝试猜测域类型,使用如下规则:
JSON type 域 type

布尔型: true 或者 false boolean

整数: 123 long

浮点数: 123.45 double

字符串,有效日期: 2014-09-15 date

字符串: foo bar string

这意味着如果你通过引号( "123" )索引一个数字,它会被映射为 string 类型,而不是 long 。但是,如果这个域已经映射为 long ,那么 Elasticsearch 会尝试将这个字符串转化为 long ,如果无法转化,则抛出一个异常。
查看映射:
通过/_mapping,我们可以查看Elasticsearch在一个或多个索引中的一个或多个类型的映射。
例如:
GET /gb/_mapping/tweet
Elasticsearch 根据我们索引的文档,为域(称为 属性 )动态生成的映射。

{
   "gb": {
      "mappings": {
         "tweet": {
            "properties": {
               "date": {
                  "type": "date",
                  "format": "strict_date_optional_time||epoch_millis"
               },
               "name": {
                  "type": "string"
               },
               "tweet": {
                  "type": "string"
               },
               "user_id": {
                  "type": "long"
               }
            }
         }
      }
   }
}

自定义域映射
尽管在很多情况下基本域数据类型已经够用,但你经常需要为单独域自定义映射,特别是字符串域。自定义映射允许你执行下面的操作:

  • 全文字符串域和精确值字符串域的区别
  • 使用特定语言分析器
  • 优化域以适应部分匹配
  • 指定自定义数据格式
  • 还有更多
    域最重要的属性是 type 。对于不是 string 的域,你一般只需要设置 type
{
    "number_of_clicks": {
        "type": "integer"
    }
}

默认, string 类型域会被认为包含全文。就是说,它们的值在索引前,会通过一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。
string 域映射的两个最重要属性是 indexanalyzer
#######index
index属性控制着如何索引字符串,它可以取下面的三个值:
analyzed:首先分析字符串,然后索引它。
not_analyzed:索引这个域,但是索引的是精确值,不会对它进行分析。
no:不索引这个域。这个域不会被搜索到。
string域index属性默认是analyzed,如果想映射这个字段为一个精确值,我们就要把它设置为not_analyzed:

{
    "tag": {
        "type":     "string",
        "index":    "not_analyzed"
    }
}

analyzer
对于analyzed字符串域,使用analyzer属性指定在搜索和索引时使用的分析器。默认使用standard分析器,但是可以使用其他的内置分析器代替它,例如whitespace,simple和english等。

{
    "tweet": {
        "type":     "string",
        "analyzer": "english"
    }
}

更新映射
当创建一个索引的时候,可以指定类型的映射,也可以使用/_mapping为新类型增加映射。我们可以更新一个映射来添加一个新域,但是不能将一个存在的域从analyzed改为not_analyzed。
为了描述指定映射的两种方式,我们先删除 gd 索引:
DELETE /gb
然后创建一个新索引,指定 tweet 域使用 english 分析器:

{
  "mappings": {
    "tweet" : {
      "properties" : {
        "tweet" : {
          "type" :    "string",
          "analyzer": "english"
        },
        "date" : {
          "type" :   "date"
        },
        "name" : {
          "type" :   "string"
        },
        "user_id" : {
          "type" :   "long"
        }
      }
    }
  }
}
通过消息体中指定的 mappings 创建了索引。

稍后,我们决定在 tweet 映射增加一个新的名为 tag 的 not_analyzed 的文本域,使用 _mapping :

PUT /gb/_mapping/tweet
{
  "properties" : {
    "tag" : {
      "type" :    "string",
      "index":    "not_analyzed"
    }
  }
}
注意,我们不需要再次列出所有已存在的域,因为无论如何我们都无法改变它们。新域已经被合并到存在的映射中。

#######复杂核心域类型
除了简单标量数据类型,JSON还有null值,数组和对象。
多值域
tag 域包含多个标签。我们可以以数组的形式索引标签:

{ "tag": [ "search", "nosql" ]}

这暗示 数组中所有的值必须是相同数据类型的 。你不能将日期和字符串混在一起。如果你通过索引数组来创建新的域,Elasticsearch 会用数组中第一个值的数据类型作为这个域的 类型 。
空域
存在null值的域称为空域
下面三种域被认为是空的,它们将不会被索引:
"null_value": null,
"empty_array": [],
"array_with_null_value": [ null ]
对象域
Elasticsearch 会动态监测新的对象域并映射它们为 对象 ,在 properties 属性下列出内部域:

{
  "gb": {
    "tweet": {
      "properties": {
        "tweet":            { "type": "string" },
        "user": { 
          "type":             "object",
          "properties": {
            "id":           { "type": "string" },
            "gender":       { "type": "string" },
            "age":          { "type": "long"   },
            "name":   {
              "type":         "object",
              "properties": {
                "full":     { "type": "string" },
                "first":    { "type": "string" },
                "last":     { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}
请求体查询

之所以被称为请求体查询,是因为大部分的参数是通过Http请求体而不是字符串来传递的。
#########空查询
空查询将返回所有索引库中的所有文档:

GET /_search
{}

只用一个查询字符串,就可以在一个、多个或者_all索引库和一个、多个或者所有types中查询:

GET /index_2014*/type1,type2/_search
{}

可以使用from和size来对参数进行分页

GET /_search
{
  "from": 30,
  "size": 10
}

查询表达式
查询表达式(Query DSL)是非常灵活又富有表现力的查询语言。
使用这种查询表达式,只需要将查询语句传递给query参数:

GET /_search
{
    "query": YOUR_QUERY_HERE
}

空查询在功能上等价于使用match_all查询,会匹配所有的文档:

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

查询语句的结构
一个查询语句的典型结构:

{ 
      QUERY_NAME:  { 
          ARGUMENT: VALUE,
          ARGUMENT: VALUE,... 
       } 
 }

如果是针对某个字段,那么它的结构如下:

{ 
      QUERY_NAME:  { 
          FIELD_NAME:  { 
              ARGUMENT: VALUE, 
              ARGUMENT: VALUE,... 
         } 
     } 
 }

举个例子,你可以使用 match 查询语句来查询 tweet 字段中包含 elasticsearch 的 tweet:

GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    }
}

#######合并查询语句
查询语句块之间可以合并组成更复杂的查询。
这些语句可以是如下的形式:
*叶子语句:被用于将查询字符串和一个字段对应
*复合语句:用于合并其他查询语句。 比如,一个 bool 语句 允许在你需要的时候组合其它语句,无论是 must 匹配、 must_not 匹配还是 should 匹配,同时它可以包含不评分的过滤器(filters):

{
    "bool": {
        "must":     { "match": { "tweet": "elasticsearch" }},
        "must_not": { "match": { "name":  "mary" }},
        "should":   { "match": { "tweet": "full text" }},
        "filter":   { "range": { "age" : { "gt" : 30 }} }
    }
}

一条复合语句可以合并任何其他查询语句,包括复合语句。
########查询与过滤
Elasticsearch 使用的查询语言(DSL)拥有一套查询组件,这些组件可以以无限组合的方式进行搭配。这套组件可以在以下两种情况下使用:过滤情况(filtering context)和查询情况(query context)。
当使用于 过滤情况 时,查询被设置成一个“不评分”或者“过滤”查询。即,这个查询只是简单的问一个问题:“这篇文档是否匹配?”。回答也是非常的简单,yes 或者 no ,二者必居其一。
当使用于 查询情况 时,查询就变成了一个“评分”的查询。和不评分的查询类似,也要去判断这个文档是否匹配,同时它还需要判断这个文档匹配的有 多好(匹配程度如何)。
一个评分查询计算每一个文档与此查询的 相关程度,同时将这个相关程度分配给表示相关性的字段 _score,并且按照相关性对匹配到的文档进行排序。这种相关性的概念是非常适合全文搜索的情况,因为全文搜索几乎没有完全 “正确” 的答案。

最重要的查询
  • match_all查询简单的匹配所有的文档,在没有指定查询方式的时候,它会是默认的查询。
{ "match_all": {}}

它经常与 filter 结合使用--例如,检索收件箱里的所有邮件。所有邮件被认为具有相同的相关性,所以都将获得分值为 1 的中性 _score

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

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

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

对于精确值的查询,你可能需要使用 filter 语句来取代 query,因为 filter 将会被缓存。接下来,我们将看到一些关于 filter 的例子。

  • multi_match查询
    multi_match查询可以在多个字段上执行相同的match查询
{
    "multi_match": {
        "query":    "full text search",
        "fields":   [ "title", "body" ]
    }
}
  • range查询
    range查询用于找出那些落在指定区间内的数字或者时间
{
    "range": {
        "age": {
            "gte":  20,
            "lt":   30
        }
    }
}

range查询被允许的操作符有:
gt :大于
gte :大于等于
lt :小于
lte :小于等于

  • 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" ] }}

terms 查询对于输入的文本不分析。它查询那些精确匹配的值(包括在大小写、重音、空格等方面的差异)。

  • exists查询和missing查询
    exists查询和missing查询被用于查找那些指定字段中有值(exists)和无值(missing)的文档,这与SQL中的 IS_NULL (missing) 和 NOT IS_NULL (exists) 在本质上具有共性:
{
    "exists":   {
        "field":    "title"
    }
}
  • 组合多查询
    现实中的查询并没有那么简单,需要将多个查询组合成单一的查询。可以使用bool查询来实现。它接收以下的参数:
    must:
    文档必须匹配这些条件才能被包含进来。
    must_not:
    文档必须不匹配这些条件才能被包含进来。
    should:
    如果满足这些语句中的任意语句,将增加_score,否则,没有任何影响,它主要用于修正每个文档的相关性。
    filter:
    必须匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档。
    相关性得分是如何组合起来的?
    每一个子查询都独立地计算文档的相关性得分,一旦他们的得分被计算出来,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" }}}
        ]
    }
}

如果没有 must 语句,那么至少需要能够匹配其中的一条 should 语句。但,如果存在至少一条 must 语句,则对 should 语句的匹配没有要求。
  • 增加带有过滤器(filtering)的查询
    如果不想因为文档的时间而影响到得分,可以使用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 语句中,我们将它转成不评分的查询,将不再影响文档的相关性排名。由于它现在是一个不评分的查询,可以使用各种对 filter 查询有效的优化手段来提升性能。
所有查询都可以借鉴这种方式。将查询移到 bool 查询的 filter 语句中,这样它就自动的转成一个不评分的 filter 了。
如果你需要通过多个不同的标准来过滤你的文档,bool 查询本身也可以被用做不评分的查询。简单地将它放置到 filter 语句中并在内部构建布尔逻辑:

{  "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"  }}  
                    ] 
              }  
          } 
     } 
 }
通过混合布尔查询,我们可以在我们的查询请求中灵活地编写 scoring 和 filtering 查询逻辑。
  • constant_score查询
    constant_score查询是将一个不变的常量评分应用于所有匹配的文档。它经常用于你只需要执行一个filter而们没有其它查询的情况下。
    可以使用它来取代只有 filter 语句的 bool 查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助。
{  
      "constant_score":  { 
             "filter":  { 
                 "term":  {  "category":  "ebooks"  }   
       }  
}  

term 查询被放置在 constant_score 中,转成不评分的 filter。这种方式可以用来取代只有 filter 语句的 bool 查询。

  • 验证查询
    查询可以变得非常的复杂,尤其是和不同的分析器与不同的字段映射结合时,理解起来就会有点困难,不过validate-queryAPI可以用来验证查询是否合法。
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"
      }
   }
}
很明显,我们将查询类型(match)与字段名称 (tweet)搞混了:
{
  "valid" :     false,
  "_shards" :   { ... },
  "explanations" : [ {
    "index" :   "gb",
    "valid" :   false,
    "error" :   "org.elasticsearch.index.query.QueryParsingException:
                 [gb] No query registered for [tweet]"
  } ]
}
  • 理解查询语句
    对于合法查询,使用 explain 参数将返回可读的描述,这对准确理解 Elasticsearch 是如何解析你的 query 是非常有用的:
GET /_validate/query?explain
{
   "query": {
      "match" : {
         "tweet" : "really powerful"
      }
   }
}

我们查询的每一个 index都会返回对应的 explanation ,因为每一个 index 都有自己的映射和分析器:

{
  "valid" :         true,
  "_shards" :       { ... },
  "explanations" : [ {
    "index" :       "us",
    "valid" :       true,
    "explanation" : "tweet:really tweet:powerful"
  }, {
    "index" :       "gb",
    "valid" :       true,
    "explanation" : "tweet:realli tweet:power"
  } ]
}

从 explanation 中可以看出,匹配 really powerful 的 match 查询被重写为两个针对 tweet 字段的 single-term 查询,一个single-term查询对应查询字符串分出来的一个term。
当然,对于索引 us ,这两个 term 分别是 really 和 powerful ,而对于索引 gb ,term 则分别是 realli 和 power 。之所以出现这个情况,是由于我们将索引 gb 中 tweet 字段的分析器修改为 english 分析器。
#######排序与相关性
默认情况下,返回的结果是按照相关性进行排序的——最相关的文档排在最前面。
########排序
相关性是一个数值,相关性得分由一个浮点数进行表示,并在搜索结果中通过_score参数返回,默认排序是_score降序。

有时,相关性评分对你来说并没有意义。例如,下面的查询返回所有 user_id 字段包含 1 的结果:
GET /_search
{
    "query" : {
        "bool" : {
            "filter" : {
                "term" : {
                    "user_id" : 1
                }
            }
        }
    }
}

这里没有一个有意义的分数:因为我们使用的是 filter (过滤),这表明我们只希望获取匹配 user_id: 1 的文档,并没有试图确定这些文档的相关性。 实际上文档将按照随机顺序返回,并且每个文档都会评为零分。
注:如果评分为零会对你造成困扰,那么可以使用constant_score查询进行代替。

GET /_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "term" : {
                    "user_id" : 1
                }
            }
        }
    }
}

这将让所有文档应用一个恒定分数(默认为 1 )。它将执行与前述查询相同的查询,并且所有的文档将像之前一样随机返回,这些文档只是有了一个分数而不是零分。

  • 按照字段的值进行排序
    通过时间来对tweets进行排序,最新的tweets排在最前面。
GET /_search
{
    "query" : {
        "bool" : {
            "filter" : { "term" : { "user_id" : 1 }}
        }
    },
    "sort": { "date": { "order": "desc" }}
}

结果如下,你会看到两点不同

"hits"  :  { 
         "total"  :  6, 
         "max_score"  :  null, 
         "hits"  :  [  {  
            "_index"  :  "us", 
            "_type"  :  "tweet", 
            "_id"  :  "14",  
            "_score"  :  null,  
            "_source"  :  { 
                   "date":  "2014-09-24",
                          ... 
             }, 
           "sort"  :  [  1411516800000  ] 
       }, 
       ... 
 }
_score不被计算,因为它并没有用于排序。
date字段的值表示为自January 1, 1970 00:00:00 UTC以来的毫秒数,通过sort字段的值进行返回。
  • 多级排序
    假定我们想要结合使用 date 和 _score 进行查询,并且匹配的结果首先按照日期排序,然后按照相关性排序:
GET /_search
{
    "query" : {
        "bool" : {
            "must":   { "match": { "tweet": "manage text search" }},
            "filter" : { "term" : { "user_id" : 2 }}
        }
    },
    "sort": [
        { "date":   { "order": "desc" }},
        { "_score": { "order": "desc" }}
    ]
}

排序条件的顺序很重要,结果首先会按第一个条件排序,仅当结果集的第一个sort值完全相同时才会按照第二个条件进行排序,以此类推。

  • 多值字段的排序
    对于数字或日期,你可以将多值字段减为单值,这可以通过使用 minmaxavg 或是 sum 排序模式。例如你可以按照每个 date 字段中的最早日期进行排序,通过以下方法:
"sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
}

#######字符串排序与对字段
被解析的字符串字段也是多值字段但是很少会按照你想要的方式进行排序。如果你想分析一个字符串,如 fine old art , 这包含 3 项。我们很可能想要按第一项的字母排序,然后按第二项的字母排序,诸如此类,但是 Elasticsearch 在排序过程中没有这样的信息。
你可以使用 minmax 排序模式(默认是 min ),但是这会导致排序以 art 或是 old ,任何一个都不是所希望的。
为了以字符串字段进行排序,这个字段应仅包含一项: 整个 not_analyzed 字符串。但是我们仍需要 analyzed 字段,这样才能以全文进行查询。
一个简单的方法是用两种方式对同一个字符串进行索引,这将在文档中包括两个字段: analyzed 用于搜索, not_analyzed 用于排序。
但是保存相同的字符串两次在 _source 字段是浪费空间的。 我们真正想要做的是传递一个 单字段 但是却用两种方式索引它。所有的 _core_field 类型 (strings, numbers, Booleans, dates) 接收一个 fields 参数
该参数允许你转化一个简单的映射如:

"tweet": {
    "type":     "string",
    "analyzer": "english"
}
为一个多字段映射如:
"tweet": {  
    "type":     "string",
    "analyzer": "english",
    "fields": {
        "raw": {
            "type":  "string",
            "index": "not_analyzed"
        }
    }
}   
tweet 主字段与之前的一样: 是一个 analyzed 全文字段。
新的 tweet.raw 子字段是 not_analyzed.
现在,至少只要我们重新索引了我们的数据,使用 tweet 字段用于搜索,tweet.raw 字段用于排序:
GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    },
    "sort": "tweet.raw"
}
相关性

每个文档都有相关性评分,用一个正浮点数字段_score来表示,_score的评分越高,相关性越高。

聚合操作:

桶:满足特定条件的文档的集合

数据库和es的对应关系:
数据库 =》 索引
表 =》 类型
行 =》 文档
列 =》 列

指标:

一、
按时间进行统计:使用date_histogram进行分析,
在时间维度上构建指标进行分析:在时间范围上构建buckets,每个bucket被定义成一个特定的日期大小
例如:

GET /cars/transactions/_search
{
   "size" : 0,
   "aggs": {
      "sales": {
         "date_histogram": {
            "field": "sold",
            "interval": "month",    //时间间隔
            "format": "yyyy-MM-dd"  
         }
      }
   }
}

我们的查询只有一个聚合,即每个月构建一个bucket,查询的结果为:

{
   ...
   "aggregations": {
      "sales": {
         "buckets": [
            {        //每个月的bucket
               "key_as_string": "2014-01-01",  //键值
               "key": 1388534400000,
               "doc_count": 1  //文档数目
            },
            {
               "key_as_string": "2014-02-01",
               "key": 1391212800000,
               "doc_count": 1
            },
            {
               "key_as_string": "2014-05-01",
               "key": 1398902400000,
               "doc_count": 1
            },
            {
               "key_as_string": "2014-07-01",
               "key": 1404172800000,
               "doc_count": 1
            },
            {
               "key_as_string": "2014-08-01",
               "key": 1406851200000,
               "doc_count": 1      
            },
            {
               "key_as_string": "2014-10-01",
               "key": 1412121600000,
               "doc_count": 1
            },
            {
               "key_as_string": "2014-11-01",
               "key": 1414800000000,
               "doc_count": 2
            }
         ]
...
}

从上面的返回结果看出,没有返回文档数目为零的buckets
需要设置下面的参数来实现这种效果

GET /cars/transactions/_search
{
   "size" : 0,
   "aggs": {
      "sales": {
         "date_histogram": {
            "field": "sold",
            "interval": "month",
            "format": "yyyy-MM-dd",
            "min_doc_count" : 0,   //强制返回空buckets
            "extended_bounds" : {   //强制返回设置的min和max时间范围内的buckets
                "min" : "2014-01-01",
                "max" : "2014-12-31"
            }
         }
      }
   }
}

buckets可以嵌套进buckets中进行更复杂的分析。
例如:构建聚合按季度展示所有的汽车品牌总销售额,同时按季度、按每个汽车品牌计算销售总额,以便查找到哪个品牌最赚钱。

GET /cars/transactions/_search
{
   "size" : 0,
   "aggs": {
      "sales": {
         "date_histogram": {
            "field": "sold",
            "interval": "quarter", //按季度构建buckets
            "format": "yyyy-MM-dd",
            "min_doc_count" : 0,
            "extended_bounds" : {
                "min" : "2014-01-01",
                "max" : "2014-12-31"
            }
         },
         "aggs": {
            "per_make_sum": {
               "terms": {
                  "field": "make"
               },
               "aggs": {
                  "sum_price": {
                     "sum": { "field": "price" } //计算每种汽车品牌的销售总额
                  }
               }
            },
            "total_sum": {
               "sum": { "field": "price" }   //计算所有品牌的销售总额
            }
         }
      }
   }
}

查询结果为:

{
....
"aggregations": {
   "sales": {
      "buckets": [
         {
            "key_as_string": "2014-01-01",
            "key": 1388534400000,
            "doc_count": 2,
            "total_sum": {
               "value": 105000
            },
            "per_make_sum": {
               "buckets": [
                  {
                     "key": "bmw",
                     "doc_count": 1,
                     "sum_price": {
                        "value": 80000
                     }
                  },
                  {
                     "key": "ford",
                     "doc_count": 1,
                     "sum_price": {
                        "value": 25000
                     }
                  }
               ]
            }
         },
...
}

二、
范围限定的聚合:基于查询匹配的文档集合进行聚合
如果没有指定查询就等价于查所有的文档。
例如:

GET /cars/transactions/_search
{
    "size" : 0,
    "aggs" : {
        "colors" : {
            "terms" : {
              "field" : "color"
            }
        }
    }
}
等价于查所有文档
GET /cars/transactions/_search
{
    "size" : 0,
    "query" : {
        "match_all" : {}
    },
    "aggs" : {
        "colors" : {
            "terms" : {
              "field" : "color"
            }
        }
    }
}

我们利用范围,进行聚合
例如:查询福特在售汽车有多少种颜色?

GET /cars/transactions/_search
{
    "query" : {
        "match" : {
            "make" : "ford"
        }
    },
    "aggs" : {
        "colors" : {
            "terms" : {
              "field" : "color"
            }
        }
    }
}

没有指定size:0,所以搜索结果和聚合结果都会被返回

{
...
   "hits": {
      "total": 2,
      "max_score": 1.6931472,
      "hits": [
         {
            "_source": {
               "price": 25000,
               "color": "blue",
               "make": "ford",
               "sold": "2014-02-12"
            }
         },
         {
            "_source": {
               "price": 30000,
               "color": "green",
               "make": "ford",
               "sold": "2014-05-18"
            }
         }
      ]
   },
   "aggregations": {
      "colors": {
         "buckets": [
            {
               "key": "blue",
               "doc_count": 1
            },
            {
               "key": "green",
               "doc_count": 1
            }
         ]
      }
   }
}

全局桶:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容