如何正确使用 ES 搜索引擎——查询
Elasticsearch 是一个使用 Java 编写的开源搜索引擎,建立在全文搜索引擎库 Apache Lucene™ 基础之上,它的内部使用 Lucene 做索引与搜索,但隐藏了 Lucene 的复杂性,取而代之的是将所有功能打包成一个单独的服务,可以通过程序与它提供的一套简单的 RESTful API 进行通信。
查询表达式(query)
查询表达式(Query DSL)是一种非常灵活的查询语言,es 使用它以简单的 json 接口来展现 Lucene 的绝大部分功能,要使用这种查询表达式,只需将查询语句传递给 query 参数:
1 | |
query DSL
在查询上下文中,查询会回答这个问题——“这个文档匹不匹配这个查询,它的相关度高吗?”
如何验证匹配很好理解,如何计算相关度呢?es 中索引的数据都会存储一个 _score 分值,分值越高就代表越匹配。另外关于某个搜索的分值计算还是很复杂的,因此也需要一定的时间。
filter DSL
在过滤器上下文中,查询会回答这个问题——“这个文档匹不匹配?”
答案很简单,是或者不是。它不会去计算任何分值,也不会关心返回的排序问题,因此效率会高一点。另外,经常使用过滤器,es 会自动的缓存过滤器的内容,会提高很多查询的性能。
查询请求结果
hits
返回结果中最重要的部分是 hits ,它包含 total 字段来表示匹配到的文档总数,并且一个 hits 数组包含所查询结果的前十个文档。在 hits 数组中每个结果包含文档的 _index 、 _type 、 _id ,加上 _source 字段,这意味着我们可以直接从返回的搜索结果中使用整个文档,而不像其他的搜索引擎,仅仅返回文档的 ID。每个结果还有一个 _score ,它衡量了文档与查询的匹配程度,max_score 表示与查询所匹配文档的 _score 的最大值。
took
took 值告诉我们执行整个搜索请求耗费了多少 毫秒。
timeout
timed_out 值告诉我们查询是否超时。默认情况下,搜索请求不会超时。如果低响应时间比完成结果更重要,你可以指定 timeout 为 10ms 或者 1s。在请求超时之前,es 将会返回已经成功从每个分片获取的结果。
shards
_shards 部分告诉我们在查询中参与分片的总数,以及这些分片成功了多少个失败了多少个。
1 | |
多索引,多类型
/_search在所有的索引中搜索所有的类型
/gb/_search在
gb索引中搜索所有的类型/gb,us/_search在
gb和us索引中搜索所有的文档/g*,u*/_search在任何以
g或者u开头的索引中搜索所有的类型/gb/user/_search在
gb索引中搜索user类型/gb,us/user,tweet/_search在
gb和us索引中搜索user和tweet类型/_all/user,tweet/_search在所有的索引中搜索
user和tweet类型
分页(from、size)
from 显示应该跳过的初始结果数量,默认是
0size 显示应该返回的结果数量,默认是
10
可以同时使用 from 和 size 参数来分页:
1 | |
精确值查找(term,terms)
查找单个精确值
可以用 term 处理数字、布尔值、日期、以及文本。
1 | |
查找多个精确值
term 查询对于查找单个值非常有用,但如果我们想要搜索多个值该如何处理呢?不需要使用多个 term 查询,我们只要用单个 terms 查询,terms 允许指定多个匹配条件,除此之外它几乎与 term 的使用方式一模一样,我们只要将 term 字段的值改为数组即可:
它几乎与 term 的使用方式一模一样,与指定单个价格不同,我们只要将 term 字段的值改为数组即可:
1 | |
当进行精确值查找时,请尽可能多的使用 过滤式查询(filters),过滤器很重要,因为它们执行速度非常快(不会计算相关度,而且可以被缓存)。
当我们不关心 TF/IDF 对搜索结果排序的影响,只想知道一个词是否在某个字段中出现过时,可以使用 constant_score 将 query 查询语句或者 filter 过滤语句包装起来,同时可以使用 boost 指定权重:
1 | |
范围(range)
range 过滤允许我们按照指定范围查找某个文档,适用于数字、日期和字符串:
1 | |
可供组合的选项如下:
gt:>大于(greater than)lt:<小于(less than)gte:>=大于等于(greater than or equal to)lte:<=小于等于(less than or equal to)
组合过滤器(bool)
布尔过滤器
一个 bool 过滤器可以由三部分组成:
must所有的语句都必须匹配,与
AND等价。must_not所有的语句都不能匹配,与
NOT等价。should至少有一个语句要匹配,与
OR等价。
使用组合过滤器进行查询:
1 | |
嵌套布尔过滤器
尽管 bool 是一个复合过滤器,可以接受多个子过滤器,但它本身仍然还只是一个过滤器。因此我们可以将一个 bool 过滤器嵌套在其他 bool 过滤器内部,这为我们提供了处理任意复杂布尔逻辑的能力。
全文匹配(match、match_all、multi_match)
match 查询
匹配查询 match 是一个 标准 查询,无论需要查询什么字段, match 查询都应该会是首选的查询方式。同时它是一个高级 全文查询 ,这表示它既能处理全文字段,又能处理精确字段。
我们用一个示例来解释使用 match 搜索全文字段中的单个词:
1 | |
可以使用
operator参数来提高精度,默认情况下该操作符是or,可以修改成and让所有指定词项都必须匹配。可以使用
minimum_should_match最小匹配参数来控制精度,以排除那些不太相关的文档。
match_all 查询
查询所有文档,是没有查询条件下的默认语句:
1 | |
multi_match 查询
multi_match查询允许你在 match 查询的基础上同时搜索多个字段:
1 | |
短语匹配(match_phrase)
就像 match 查询对于标准全文检索是一种最常用的查询一样,当你想寻找邻近的几个单词时,就会使用 match_phrase 查询:
1 | |
和 match 查询类似,match_phrase 查询首先将查询字符串解析成一个词项列表,然后对这些词项进行搜索,但只保留那些包含 全部 搜索词项,且 位置 与搜索词项相同的文档。比如对于 quick fox 的短语搜索可能不会匹配到任何文档,因为没有文档包含的 quick 词之后紧跟着 fox 。
近似匹配(prefix、wildcards、regexp)
prefix 前缀查询
prefix 查询是一个词级别的底层的查询,它不会在搜索之前分析查询字符串,而是假定传入前缀就正是要查找的前缀。为了找到所有以 W1 开始的邮编,可以使用简单的 prefix 查询:
1 | |
wildcard 通配符查询
与 prefix 前缀查询的特性类似,wildcard 通配符查询也是一种底层基于词的查询,与前缀查询不同的是它使用标准的 shell 通配符查询,允许匹配指定的正则式,其中:
*匹配多个任意字符(包括零个或一个)?匹配一个任意字符(不包括零个)
这个查询会匹配包含 W1F 7HW 和 W2F 8HW 的文档:
1 | |
regexp 正则表达式查询
假如现在只想匹配 W 区域的所有邮编(只以 W 开始并跟随一个数字),prefix 前缀匹配可能会包括以 WC 开头的所有邮编,wildcard 通配符匹配也可能会遇到同样的问题,如果想匹配,这时可以使用 regexp 正则表达式查询写出更复杂的模式:
1 | |
处理 Null 值(exists、missing)
存在查询
exists 查询可以用于查找文档中是否包含指定字段:
1 | |
缺失查询
missing 查询本质上与 exists 恰好相反,它返回没有某个字段的文档:
1 | |
聚合和过滤(aggs、filter)
agg 聚合
聚合操作被置于顶层参数 aggs 之下,可以为聚合指定一个我们想要名称,然后定义单个桶的类型 terms。比如汽车经销商可能会想知道哪个颜色的汽车销量最好,用聚合可以轻易得到结果:
1 | |
在本例中,我们使用 popular_colors 作为聚合的名字,并定义了一个 terms 桶,这个 terms 桶会使用 color 字段为每个颜色动态创建新桶。
filter 过滤
聚合范围限定还有一个自然的扩展就是过滤,因为聚合是在查询结果范围内操作的,任何可以适用于查询的过滤器也可以应用在聚合上。如果我们想找到售价在 $10,000 美元之上的所有汽车同时为这些车计算平均售价,可以简单地使用一个 constant_score 查询和 filter 约束:
1 | |
avg为均值度量,min为最小度量,max为最大度量。
排序(sort)
简单排序
我们可以简单的使用 sort 参数进行实现按照字段的值排序:
1 | |
多级排序
假定我们想要结合使用 date 和 _score 进行查询,并且匹配的结果首先按照日期排序,然后按照相关性排序:
1 | |
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!