elasticsearch权威指南

elasticsearch 权威指南

3.数据

3.5更新

文档在Elasticsearch中是不可变的——我们不能修改他们。如果需要更新已存在的文档,我们可以使用《索引文档》章节提到的index API 重建索引(reindex) 或者替换掉它。

1
2
3
4
5
6
PUT /website/blog/123
{
"title": "My first blog entry",
"text": "I am starting to get the hang of this...",
"date": "2014/01/02"
}

在响应中,我们可以看到Elasticsearch把_version增加了。

1

1
2
3
4
5
6
7
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 2,
"created": false <1>
}
  • <1> created标识为false因为同索引、同类型下已经存在同ID的文档。

在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。

在本章的后面,我们将会在《局部更新》中探讨update API。这个API 似乎 允许你修改文档的局部,但事实上Elasticsearch遵循与之前所说完全相同的过程,这个过程如下:

  1. 从旧文档中检索JSON
  2. 修改它
  3. 删除旧文档
  4. 索引新文档
3.6创建
1
2
3
4
PUT /website/blog/123/_create
{
...
}
3.9局部更新
文档局部更新

在《更新文档》一章,我们说了一种通过检索,修改,然后重建整文档的索引方法来更新文档。这是对的。然而,使用update API,我们可以使用一个请求来实现局部更新,例如增加数量的操作。

我们也说过文档是不可变的——它们不能被更改,只能被替换。update API必须遵循相同的规则。表面看来,我们似乎是局部更新了文档的位置,内部却是像我们之前说的一样简单的使用update API处理相同的检索-修改-重建索引流程,我们也减少了其他进程可能导致冲突的修改。

最简单的update请求表单接受一个局部文档参数doc,它会合并到现有文档中——对象合并在一起,存在的标量字段被覆盖,新字段被添加。举个例子,我们可以使用以下请求为博客添加一个tags字段和一个views字段:

1
2
3
4
5
6
7
POST /website/blog/1/_update
{
"doc" : {
"tags" : [ "testing" ],
"views": 0
}
}
使用脚本更新

脚本能够使用update API改变_source字段的内容,它在脚本内部以ctx._source表示。例如,我们可以使用脚本增加博客的views数量:

1
2
3
4
POST /website/blog/1/_update
{
"script" : "ctx._source.views+=1"
}
1
2
3
4
5
6
7
POST /website/blog/1/_update
{
"script" : "ctx._source.tags+=new_tag",
"params" : {
"new_tag" : "search"
}
}
3.10 MGET

检索多个文档

mget API参数是一个docs数组,数组的每个节点定义一个文档的_index_type_id元数据。如果你只想检索一个或几个确定的字段,也可以定义一个_source参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /_mget
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : 2
},
{
"_index" : "website",
"_type" : "pageviews",
"_id" : 1,
"_source": "views"
}
]
}

响应体也包含一个docs数组,每个文档还包含一个响应,它们按照请求定义的顺序排列。每个这样的响应与单独使用get request响应体相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"docs" : [
{
"_index" : "website",
"_id" : "2",
"_type" : "blog",
"found" : true,
"_source" : {
"text" : "This is a piece of cake...",
"title" : "My first external blog entry"
},
"_version" : 10
},
{
"_index" : "website",
"_id" : "1",
"_type" : "pageviews",
"found" : true,
"_version" : 2,
"_source" : {
"views" : 2
}
}
]
}
3.11 批量

bulk请求体如下,它有一点不同寻常:

1
2
3
4
5
{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n
...

这种格式类似于用"\n"符号连接起来的一行一行的JSON文档流(stream)。两个重要的点需要注意:

  • 每行必须以"\n"符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。
  • 每一行的数据不能包含未被转义的换行符,它们会干扰分析——这意味着JSON不能被美化打印。

删除操作不需要请求体(request body)

每个子请求都被独立的执行,所以一个子请求的错误并不影响其它请求。如果任何一个请求失败,顶层的error标记将被设置为true,然后错误的细节将在相应的请求中被报告。

1
2
3
4
5
6
7
8
POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }} <1>
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }}
{ "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} }
{ "doc" : {"title" : "My updated blog post"} }

5.搜索

5.1空搜索

1
GET /_search

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"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
}

5.2 多搜索和多索引

1
/gb,us/user,tweet/_search

5.3分页

Elasticsearch接受fromsize参数:

size: 结果数,默认10

from: 跳过开始的结果数,默认0

5.4 查询字符串

search API有两种表单:一种是“简易版”的查询字符串(query string)将所有参数通过查询字符串定义,另一种版本使用JSON完整的表示请求体(request body),这种富搜索语言叫做结构化查询语句(DSL)

tweet字段中包含elasticsearch字符。

1
GET /_all/tweet/_search?q=tweet:elasticsearch

6.映射和分析

6.4 分析

当我们索引(index)一个文档,全文字段会被分析为单独的词来创建倒排索引。不过,当我们在全文字段搜索(search)时,我们要让查询字符串经过同样的分析流程处理,以确保这些词在索引中存在。

  • 当你查询全文(full text)字段,查询将使用相同的分析器来分析查询字符串,以产生正确的词列表。
  • 当你查询一个确切值(exact value)字段,查询将不分析查询字符串,但是你可以自己指定。

6.5 映射

string类型的字段,默认的,考虑到包含全文本,它们的值在索引前要经过分析器分析,并且在全文搜索此字段前要把查询语句做分析处理。

string类型默认进行analyze,即全文搜索。而其他字段则是精确匹配。

index

index参数控制字符串以何种方式被索引。

解释
analyzed 首先分析这个字符串,然后索引。换言之,以全文形式索引此字段。
not_analyzed 索引这个字段,使之可以被搜索,但是索引内容和指定值一样。不分析此字段。
no 不索引这个字段。这个字段不能为搜索到。

string类型字段默认值是analyzed。如果我们想映射字段为确切值(即不分词),我们需要设置它为not_analyzed

分析

对于analyzed类型的字符串字段,使用analyzer参数来指定哪一种分析器将在搜索和索引的时候使用。默认的,Elasticsearch使用standard分析器。

6.6 复合类型

多层对象

内部对象(inner objects)经常用于在另一个对象中嵌入一个实体或对象

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"tweet": "Elasticsearch is very flexible",
"user": {
"id": "@johnsmith",
"gender": "male",
"age": 26,
"name": {
"full": "John Smith",
"first": "John",
"last": "Smith"
}
}
}
内部对象的映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"gb": {
"tweet": { <1>
"properties": {
"tweet": { "type": "string" },
"user": { <2>
"type": "object",
"properties": {
"id": { "type": "string" },
"gender": { "type": "string" },
"age": { "type": "long" },
"name": { <3>
"type": "object",
"properties": {
"full": { "type": "string" },
"first": { "type": "string" },
"last": { "type": "string" }
}
}
}
}
}
}
}
}

<1> 根对象.

<2><3> 内部对象.

内部对象怎么被索引

Lucene 并不了解内部对象。 一个 Lucene 文件包含一个键-值对应的扁平表单。

1
2
3
4
5
6
7
8
9
{
"tweet": [elasticsearch, flexible, very],
"user.id": [@johnsmith],
"user.gender": [male],
"user.age": [26],
"user.name.full": [john, smith],
"user.name.first": [john],
"user.name.last": [smith]
}

7.结构化查询

7.2结构化查询

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"bool": {
"must": { "match": { "email": "business opportunity" }},
"should": [
{ "match": { "starred": true }},
{ "bool": {
"must": { "folder": "inbox" }},
"must_not": { "spam": true }}
}}
],
"minimum_should_match": 1
}
}

7.3查询与过滤

原则上来说,使用查询语句做全文本搜索或其他需要进行相关性评分的时候,剩下的全部用过滤语句。

一条过滤语句会询问每个文档的字段值是否包含着特定值:

  • created 的日期范围是否在 20132014 ?
  • status 字段中是否包含单词 “published” ?
  • lat_lon 字段中的地理位置与目标点相距是否不超过10km ?

一条查询语句与过滤语句相似,但问法不同:

查询语句会询问每个文档的字段值与特定值的匹配程度如何?

查询语句的典型用法是为了找到文档:

  • 查找与 full text search 这个词语最佳匹配的文档
  • 查找包含单词 run ,但是也包含runs, running, jogsprint的文档
  • 同时包含着 quick, brownfox — 单词间离得越近,该文档的相关性越高
  • 标识着 lucene, searchjava — 标识词越多,该文档的相关性越高

7.4重要的查询过滤子句

term过滤

term主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed的字符串(未经分析的文本数据类型)

terms过滤

termsterm 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配。

1
2
3
4
5
{
"terms": {
"tag": [ "search", "full_text", "nosql" ]
}
}

range过滤

1
2
3
4
5
6
7
8
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}

exists和missing过滤

existsmissing 过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL条件。

这两个过滤只是针对已经查出一批数据来,但是想区分出某个字段是否存在的时候使用。

bool过滤

match_all查询

match查询

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。

如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符。

如果用match下指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed 的字符串时,它将为你搜索你给定的值。

做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据。

multi_match 查询

multi_match查询允许你做match查询的基础上同时搜索多个字段:

1
2
3
4
5
6
{
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
}

bool查询

bool 查询与 bool 过滤相似,用于合并多个查询子句。不同的是,bool 过滤可以直接给出是否匹配成功, 而bool 查询要计算每一个查询子句的 _score (相关性分值)。

7.5 过滤与查询

search API中只能包含 query 语句,所以我们需要用 filtered 来同时包含 “query” 和 “filter” 子句。

1
2
3
4
5
6
{
"filtered": {
"query": { "match": { "email": "business opportunity" }},
"filter": { "term": { "folder": "inbox" }}
}
}

我们在外层再加入 query 的上下文关系:

1
2
3
4
5
6
7
8
9
GET /_search
{
"query": {
"filtered": {
"query": { "match": { "email": "business opportunity" }},
"filter": { "term": { "folder": "inbox" }}
}
}
}

7.6验证查询

validate API 可以验证一条查询语句是否合法。

1
2
3
4
5
6
7
8
GET /gb/tweet/_validate/query
{
"query": {
"tweet" : {
"match" : "really powerful"
}
}
}

想知道语句非法的具体错误信息,需要加上 explain 参数:

1
2
3
4
5
6
7
8
GET /gb/tweet/_validate/query?explain <1>
{
"query": {
"tweet" : {
"match" : "really powerful"
}
}
}

explain参数可以帮助我们理解语句是否正确。

8.排序

8.2.字符串排序

为了使一个string字段可以进行排序,它必须只包含一个词:即完整的not_analyzed字符串(译者注:未经分析器分词并排序的原字符串)。 当然我们需要对字段进行全文本搜索的时候还必须使用被 analyzed 标记的字段。

_source 下相同的字符串上排序两次会造成不必要的资源浪费。 而我们想要的是同一个字段中同时包含这两种索引方式,我们只需要改变索引(index)的mapping即可。 方法是在所有核心字段类型上,使用通用参数 fields对mapping进行修改。 比如,我们原有mapping如下:

1
2
3
4
"tweet": {
"type": "string",
"analyzer": "english"
}

改变后的多值字段mapping如下:

1
2
3
4
5
6
7
8
9
10
"tweet": { <1>
"type": "string",
"analyzer": "english",
"fields": {
"raw": { <2>
"type": "string",
"index": "not_analyzed"
}
}
}

<1> tweet 字段用于全文本的 analyzed 索引方式不变。

<2> 新增的 tweet.raw 子字段索引方式是 not_analyzed

现在,在给数据重建索引后,我们既可以使用 tweet 字段进行全文本搜索,也可以用tweet.raw字段进行排序:

1
2
3
4
5
6
7
8
9
GET /_search
{
"query": {
"match": {
"tweet": "elasticsearch"
}
},
"sort": "tweet.raw"
}

警告: 对 analyzed 字段进行强制排序会消耗大量内存。

8.3相关性

理解评分

当调试一条复杂的查询语句时,想要理解相关性评分 _score 是比较困难的。ElasticSearch 在 每个查询语句中都有一个explain参数,将 explain 设为 true 就可以得到更详细的信息。

1
2
3
4
GET /_search?explain <1>
{
"query" : { "match" : { "tweet" : "honeymoon" }}
}

<1> explain 参数可以让返回结果添加一个 _score 评分的得来依据。

提示: JSON形式的explain描述是难以阅读的 但是转成 YAML 会好很多,只需要在参数中加上format=yaml

Explain api

explain选项加到某一文档上时,它会告诉你为何这个文档会被匹配,以及一个文档为何没有被匹配。如请求路径为 /index/type/id/_explain

10.索引管理

10.1 创建删除
1
2
3
4
5
6
7
8
PUT /my_index
{
"settings": { ... any settings ... },
"mappings": {
"type_one": { ... any mappings ... },
"type_two": { ... any mappings ... },
...
}
10.3配置分析器
1
2
GET /spanish_docs/_analyze?analyzer=es_std
El veloz zorro marrón
10.7 元数据中的source字段

禁用_source字段

1
2
3
4
5
6
7
8
9
10
PUT /my_index
{
"mappings": {
"my_type": {
"_source": {
"enabled": false
}
}
}
}

限制返回的字段

1
2
3
4
5
GET /_search
{
"query": { "match_all": {}},
"_source": [ "title", "created" ]
}
10.8 元数据中的all字段
1
2
3
4
5
6
GET /_search
{
"match": {
"_all": "john smith marketing"
}
}

12.结构化搜索

通过结构化搜索,你的查询结果始终是 是或非;是否应该属于集合。结构化搜索不关心文档的相关性或分数,它只是简单的包含或排除文档。

过滤器filter有term(terms),bool,range等等。

12.1查询准确值

当term用于查询字符串的时候,记得把term里面field设为not analyzed.

1
2
3
4
5
6
7
8
9
10
11
12
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"term" : {
"productID" : "XHDK-A-1293-#fJ3"
}
}
}
}
}

term(terms)是包含操作,不是等于操作。

12.2 组合过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /my_store/products/_search
{
"query" : {
"filtered" : { <1>
"filter" : {
"bool" : {
"should" : [
{ "term" : {"price" : 20}}, <2>
{ "term" : {"productID" : "XHDK-A-1293-#fJ3"}} <2>
],
"must_not" : {
"term" : {"price" : 30} <3>
}
}
}
}
}
}
12.3查询多个准确值
1
2
3
4
5
6
7
8
9
10
11
12
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"terms" : { <1>
"price" : [20, 30]
}
}
}
}
}
12.4 包含而不是相等

假如你有一个 term 过滤器 { "term" : { "tags" : "search" } },它将匹配下面两个文档:

1
2
{ "tags" : ["search"] }
{ "tags" : ["search", "open_source"] } <1>

<1> 虽然这个文档除了 search 还有其他短语,它还是被返回了

termterms必须包含 操作,而不是 必须相等

完全匹配

假如你真的需要完全匹配这种行为,最好是通过添加另一个字段来实现。在这个字段中,你索引原字段包含值的个数。引用上面的两个文档,我们现在包含一个字段来记录标签的个数:

1
2
{ "tags" : ["search"], "tag_count" : 1 }
{ "tags" : ["search", "open_source"], "tag_count" : 2 }

一旦你索引了标签个数,你可以构造一个 bool 过滤器来限制短语个数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /my_index/my_type/_search
{
"query": {
"filtered" : {
"filter" : {
"bool" : {
"must" : [
{ "term" : { "tags" : "search" } }, <1>
{ "term" : { "tag_count" : 1 } } <2>
]
}
}
}
}
}
12.5范围

Elasticsearch 有一个 range 过滤器,让你可以根据范围过滤:

1
2
3
4
5
6
"range" : {
"price" : {
"gt" : 20,
"lt" : 40
}
}

range 过滤器既能包含也能排除范围,通过下面的选项:

  • gt: > 大于
  • lt: < 小于
  • gte: >= 大于或等于
  • lte: <= 小于或等于

range 过滤器也可以用于日期字段:

1
2
3
4
5
6
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-07 00:00:00"
}
}

当用于日期字段时,range 过滤器支持日期数学操作。例如,我们想找到所有最近一个小时的文档:

1
2
3
4
5
"range" : {
"timestamp" : {
"gt" : "now-1h"
}
}

这个过滤器将始终能找出所有时间戳大于当前时间减 1 小时的文档,让这个过滤器像移窗一样通过你的文档。

日期计算也能用于实际的日期,而不是仅仅是一个像 now 一样的占位符。只要在日期后加上双竖线 ||,就能使用日期数学表达式了。

1
2
3
4
5
6
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-01 00:00:00||+1M" <1>
}
}

<1> 早于 2014 年 1 月 1 号加一个月

12.8 过滤顺序

bool 条件中过滤器的顺序对性能有很大的影响。更详细的过滤条件应该被放置在其他过滤器之前,以便在更早的排除更多的文档。

假如条件 A 匹配 1000 万个文档,而 B 只匹配 100 个文档,那么需要将 B 放在 A 前面。

缓存的过滤器非常快,所以它们需要被放在不能缓存的过滤器之前。

13.全文检索

全文检索:怎样对全文字段(full-text fields)进行检索以找到相关度最高的文档。

全文检索最重要的两个方面是:

  • 相关度(Relevance)

    根据文档与查询的相关程度对结果集进行排序的能力。相关度可以使用TF/IDF、地理位置相近程度、模糊相似度或其他算法计算。

  • 分析(Analysis)

    将一段文本转换为一组唯一的、标准化了的标记(token),用以(a)创建倒排索引,(b)查询倒排索引。

注意,一旦提到相关度和分析,指的都是查询(queries)而非过滤器(filters)。

准确值('not_analyzed') 全文('analyzed')

1.基于短语的查询

不会有分析阶段。这些查询在单一的短语上执行,term查询只在倒排查询里精确地查找特定短语(或者说精确包含),而不会匹配短语的其它变形。

2.全文检索

matchquery_string这样的查询是高级查询,它们会对字段进行分析:

  • 如果检索一个'date''integer'字段,它们会把查询语句作为日期或者整数格式数据。

  • 如果检索一个准确值('not_analyzed')字符串字段,它们会把整个查询语句作为一个短语。

  • 如果检索一个全文('analyzed')字段,查询会先用适当的解析器解析查询语句,产生需要查询的短语列表。然后对列表中的每个短语执行低级查询,合并查询结果,得到最终的文档相关度。

    我们很少需要直接使用基于短语的查询。通常我们会想要检索全文,而不是单独的短语,使用高级的全文检索会更简单(全文检索内部最终还是使用基于短语的查询)。

    如果确实要查询一个准确值字段('not_analyzed'),需要考虑使用查询还是过滤器。

    [提示] 单一短语的查询通常相当于是/否问题,用过滤器可以更好的描述这类查询,并且过滤器缓存可以提升性能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    GET /_search
    {
    "query": {
    "filtered": {
    "filter": {
    "term": { "gender": "female" }
    }
    }
    }
    }
分析器

有时,在索引时和搜索时使用不同的分析器是合理的。比如,在索引时我们希望索引到同义词(在quick出现的地方,我们希望同时索引fast,rapid和speedy)。但在搜索时,我们不需要搜索所有的同义词。我们只需要关注用户输入的单词,无论是quick,fast,rapid,还是speedy,输入什么就是什么。

为了区分,ElasticSearch支持参数 index_analyzersearch_analyzer,以及 default_indexdefault_search的分析器。

被破坏的相关度(Relevance is Broken)

在我们讨论更复杂的多字段查询之前,这里先解释一下为什么我们将测试数据只创建在一个主分片上(primary shard)。

用户会时不时的抱怨遇到这样的问题:用户索引了一些文档,运行了一个简单的查询,然后明显发现一些低相关性的结果出现在高相关性结果之上。

为了理解为什么会这样,我们可以想象一下,我们在两个主分片上创建了索引,总共10个文档,其中6个文档有单词foo,可能是shard 1有其中3个文档,而shard 2有其中另外3个文档,换句话说,所有文档是均匀分布存储的。

在何为相关性中,我们描述了ElasticSearch默认的相似度算法,这个算法叫做 TF/IDF (term frequency/inverse document frequency)。详细参照之前提到的内容。

但是由于性能原因,ElasticSearch在计算IDF时,不会计算所有的文档。相反,每个分片会根据该分片的所有文档,计算一个本地的IDF。

因为我们的文档是均匀的分布存储的,每个shard的IDF是相同的。如果有5个文档存在于shard 1,而第6个文档存在于shard 2,在这种场景下,foo在一个shard里非常普通(所以不那么重要),但是在另个shard里非常少(所以会显得更重要)。这样局部IDF的差异会导致不正确的结果。

在实际中,这不是一个问题,本地和全局的IDF的差异随着索引里文档数的增多会渐渐消失,在真实世界的数据量下,局部的IDF会迅速被均化,所以这里的问题并不是相关性被破坏了,而是数据量太小了。

为了测试,我们可以通过两种方式解决这个问题。第一种是只在主分片上创建索引,就如我们例子里做的那样,如果只有一个shard,那么本地的IDF就是全局的IDF。

第二个方式就是在搜索请求后添加 ?search_type=dfs_query_then_fetchdfs 是指 分布式频率搜索(Distributed Frequency Search),它告诉ElasticSearch,先分别获得每个shard本地的IDF,然后根据结果再计算全局的IDF。

注意:
不要在产品上使用第二种方式,我们完全无须如此。

14.多字段搜索

14.1多字符串查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { (1)
"title": {
"query": "War and Peace",
"boost": 2
}}},
{ "match": { (1)
"author": {
"query": "Leo Tolstoy",
"boost": 2
}}},
{ "bool": { (2)
"should": [
{ "match": { "translator": "Constance Garnett" }},
{ "match": { "translator": "Louise Maude" }}
]
}}
]
}
}
}

为什么将译者条件语句放入另一个独立的 bool 查询中呢?所有的4个 match 查询都是 should 语句,我们为什么不将translator语句与其他语句(如 titleauthor)放在同一层呢?

答案在于分数的计算方式。bool 查询执行每个 match 查询,然后把他们加在一起,然后将结果与所有匹配的语句数量相乘,再除以所有的语句数量。处于同一层的每条语句具有相同的权重。在上面这个例子中,包含translator语句的 bool 查询,只占总分数的三分之一,如果我们将translator语句与 titleauthor 两个语句放入同一层,那么 titleauthor 语句只贡献四分之一。

14.2单一查询字符串

近似度匹配

部分匹配

补充

elasticsearch 设置 mapping 时的 store 属性

elasticsearch的store属性跟_source字段

后续的翻译:

Elasticsearch权威指南翻译目录

ElasticSearch 2 (17) - 深入搜索系列之部分匹配

对权威指南的总结博客

ElasticSearch的搜索和过滤