Elasticsearch的结构化查询
文章目录
前言
提示:本文测试环境是在ES7.9的环境下,各种命令都是经过亲自测试实现的,所以有兴趣的可以自己实现一下,根据elastic官网中的一些例子做的一些测试操作,官网中一些命令在测试环境中都已经抛弃不用了。
例如type:“string”,以及missing命令也都在7.9版本中抛弃不再使用。
一、精确值查询
在Es中实现精确查询一般通过term实现。可以用term查询处理数字(numbers)、布尔值(Booleans)、日期(dates)以及文本(text)。
先插入一些测试数据:
POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }
然后通过term方式操作数字查询这些数据:
GET /my_store/products/_search
{
"query" : {
"constant_score" : {
"filter" : {
"term" : {
"price" : 20
}
}
}
}
}
以上查询语句中通过constant_score将term转化为过滤器。
通过term方式操作文本查询:
GET /my_store/products/_search
{
"query" : {
"constant_score" : {
"filter" : {
"term" : {
"productID" : "XHDK-A-1293-#fJ3"
}
}
}
}
}
但这里有个小问题:我们无法获得期望的结果。为什么呢?问题不在 term 查询,而在于索引数据的方式。 如果我们使用 analyze API (分析 API),我们可以看到这里的 UPC 码被拆分成多个更小的 token。
GET /my_store/_analyze
{
"field": "productID",
"text": "XHDK-A-1293-#fJ3"
}
这里有几点需要注意:
• Elasticsearch 用 4 个不同的 token 而不是单个 token 来表示这个 UPC 。
• 所有字母都是小写的。
• 丢失了连字符和哈希符( # )。
所以当我们用 term 查询查找精确值 XHDK-A-1293-#fJ3 的时候,找不到任何文档,因为它并不在我们的倒排索引中,正如前面呈现出的分析结果,索引里有四个 token 。
显然这种对 ID 码或其他任何精确值的处理方式并不是我们想要的。
为了避免这种问题,我们需要告诉 Elasticsearch 该字段具有精确值,要将其设置成 not_analyzed 无需分析的。 我们可以在 自定义字段映射 中查看它的用法。为了修正搜索结果,我们需要首先删除旧索引(因为它的映射不再正确)然后创建一个能正确映射的新索引:
DELETE /my_store
PUT /my_store
{
"mappings" : {
"properties" : {
"productID" : {
"type" : "keyword",
"index" : true
}
}
}
}
然后新增数据
POST /my_store/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }
重新执行查询语句:
GET /my_store/_search
{
"query" : {
"constant_score" : {
"filter" : {
"term" : {
"productID" : "XHDK-A-1293-#fJ3"
}
}
}
}
}
1.内部过滤器的操作
在内部,Elasticsearch 会在运行非评分查询的时执行多个操作:
- 查找匹配文档.
term 查询在倒排索引中查找 XHDK-A-1293-#fJ3 然后获取包含该 term 的所有文档。本例中,只有文档 1 满足我们要求。 - 创建 bitset.
过滤器会创建一个 bitset (一个包含 0 和 1 的数组),它描述了哪个文档会包含该 term 。匹配文档的标志位是 1 。本例中,bitset 的值为 [1,0,0,0] 。在内部,它表示成一个 “roaring bitmap”,可以同时对稀疏或密集的集合进行高效编码。 - 迭代 bitset(s)
一旦为每个查询生成了 bitsets ,Elasticsearch 就会循环迭代 bitsets 从而找到满足所有过滤条件的匹配文档的集合。执行顺序是启发式的,但一般来说先迭代稀疏的 bitset (因为它可以排除掉大量的文档)。 - 增量使用计数.
Elasticsearch 能够缓存非评分查询从而获取更快的访问,但是它也会不太聪明地缓存一些使用极少的东西。非评分计算因为倒排索引已经足够快了,所以我们只想缓存那些我们 知道 在将来会被再次使用的查询,以避免资源的浪费。
为了实现以上设想,Elasticsearch 会为每个索引跟踪保留查询使用的历史状态。如果查询在最近的 256 次查询中会被用到,那么它就会被缓存到内存中。当 bitset 被缓存后,缓存会在那些低于 10,000 个文档(或少于 3% 的总索引数)的段(segment)中被忽略。这些小的段即将会消失,所以为它们分配缓存是一种浪费。
二、组合过滤器查询
组个过滤器查询可以理解为sql语句中where后面的条件存在多个。
1.bool过滤器
在es中存在一个bool过滤器,该过滤器由三部分组成
{
"bool" : {
"must" : [],
"should" : [],
"must_not" : [],
}
}
- must所有的语句都 必须(must) 匹配,与 AND 等价。
- must_not所有的语句都 不能(must not) 匹配,与 NOT 等价。
- should至少有一个语句要匹配,与 OR 等价。
当我们需要多个过滤器时,只须将它们置入 bool 过滤器的不同部分即可。
执行查询语句:
GET /my_store/_search
{
"query" : {
"bool" : {
"should" : [
{ "term" : {"price" : 20}},
{ "term" : {"productID" : "XHDK-A-1293-#fJ3"}}
],
"must_not" : {
"term" : {"price" : 30}
}
}
}
}
2.组合bool过滤器
实现语句为:
GET /my_store/_search
{
"query" : {
"bool" : {
"should" : [
{ "term" : {"productID" : "KDKE-B-9947-#kL5"}},
{ "bool" : {
"must" : [
{ "term" : {"productID" : "JODL-X-1937-#pV7"}},
{ "term" : {"price" : 30}}
]
}}
]
}
}
}
三、多个精确值查询
term 查询对于查找单个值非常有用,但通常我们可能想搜索多个值。 如果我们想要查找价格字段值为 $20 或 $30 的文档该如何处理呢?
不需要使用多个 term 查询,我们只要用单个 terms
查询(注意末尾的 s ), terms 查询好比是 term 查询的复数形式(以英语名词的单复数做比)。
它几乎与 term 的使用方式一模一样,与指定单个价格不同,我们只要将 term 字段的值改为数组即可:
GET /my_store/_search
{
"query" : {
"constant_score" : {
"filter" : {
"terms" : {
"price" : [20, 30]
}
}
}
}
}
一定要了解 term 和 terms 是 包含(contains) 操作,而非 等值(equals) (判断)。 如何理解这句话呢?
如果我们有一个 term(词项)过滤器 { “term” : { “tags” : “search” } } ,它会与以下两个文档 同时 匹配:
{ “tags” : [“search”] }
{ “tags” : [“search”, “open_source”] }
Elasticsearch 会在倒排索引中查找包括某 term 的所有文档,然后构造一个 bitset 。在我们的例子中,倒排索引表如下:
这里解释一下什么是倒排索引:
倒排索引的理解:正排索引是根据文档查询索引,而倒排索引是根据索引查询文档。举例来说,如果文档与索引是key-value的关系,那么正排索引的key就是文档,value就是文档中的索引值;那么倒排索引则正好反过来,key是文档中的索引,value是文档
对应关系如下:
正排索引:
倒排索引:
当 term 查询匹配标记 search 时,它直接在倒排索引中找到记录并获取相关的文档 ID,如倒排索引所示,这里文档 1 和文档 2 均包含该标记,所以两个文档会同时作为结果返回。
所以要实现精确相等时,可以使用term来实现,通过term对应其中的值来实现精确的查询。
GET /my_index/my_type/_search
{
"query": {
"constant_score" : {
"filter" : {
"bool" : {
"must" : [
{ "term" : { "tags" : "search" } },
{ "term" : { "tag_count" : 1 } }
]
}
}
}
}
}
四、范围查询
对数字范围进行过滤有时会更有用。例如,我们可能想要查找所有价格大于 $20 且小于 $40 美元的产品。
在sql中也经常会使用范围查询比如查询价格大于2小于5的商品。
而在es中则通过range 来实现范围查询
range 查询可同时提供包含(inclusive)和不包含(exclusive)这两种范围表达式,可供组合的选项如下:
• gt: > 大于(greater than)
• lt: < 小于(less than)
• gte: >= 大于或等于(greater than or equal to)
• lte: <= 小于或等于(less than or equal to)
查询语句如下:
GET /my_store/_search
{
"query" : {
"constant_score" : {
"filter" : {
"range" : {
"price" : {
"gte" : 20,
"lt" : 40
}
}
}
}
}
}
range 查询同样可以应用在日期字段上:
GET /my_store/_search
{
"query" : {
"constant_score" : {
"filter" : {
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-07 00:00:00"
}
}
}
}
}
}
range 查询同样可以处理字符串字段:
字符串范围可采用 字典顺序(lexicographically) 或字母顺序(alphabetically)。
例如,下面这些字符串是采用字典序(lexicographically)排序的:
• 5, 50, 6, B, C, a, ab, abb, abc, b
在倒排索引中的词项就是采取字典顺序(lexicographically)排列的,这也是字符串范围可以使用这个顺序来确定的原因。
如果我们想查找从 a 到 b (不包含)的字符串,同样可以使用 range 查询语法:
"range" : {
"title" : {
"gte" : "a",
"lt" : "b"
}
}
五、处理Null值
如果一个字段没有值,那么如何将它存入倒排索引中的呢?
这是个有欺骗性的问题,因为答案是:什么都不存。让我们看看之前内容里提到过的倒排索引:
如何将某个不存在的字段存储在这个数据结构中呢?无法做到!简单的说,一个倒排索引只是一个 token 列表和与之相关的文档信息,如果字段不存在,那么它也不会持有任何 token,也就无法在倒排索引结构中表现。
最终,这也就意味着,null, [] (空数组)和 [null] 所有这些都是等价的,它们无法存于倒排索引中。
显然,数据往往会有缺失字段,或有显式的空值或空数组。为了应对这些状况,Elasticsearch 提供了一些工具来处理空或缺失值。
1.存在查询
exists 存在查询。这个查询会返回那些在指定字段有任何值的文档,让我们索引一些示例文档并用标签的例子来说明:
POST /my_index/posts/_bulk
{ "index": { "_id": "1" }}
{ "tags" : ["search"] }
{ "index": { "_id": "2" }}
{ "tags" : ["search", "open_source"] }
{ "index": { "_id": "3" }}
{ "other_field" : "some data" }
{ "index": { "_id": "4" }}
{ "tags" : null }
{ "index": { "_id": "5" }}
{ "tags" : ["search", null] }
exits查询如下:
GET /my_index/posts/_search
{
"query" : {
"constant_score" : {
"filter" : {
"exists" : { "field" : "tags" }
}
}
}
}
以上文档集合中 tags 字段对应的倒排索引如下:
2.缺失查询
缺失 查询本质上与 exists 恰好相反:它返回某个特定 无 值字段的文档,与以下 SQL 表达的意思类似:
SELECT tags
FROM posts
WHERE tags IS NULL
因此es实现时只要通过bool过滤器将exit过滤掉就可以实现缺失查询
es实现语句如下:
GET /my_index/posts/_search
{
"query" : {
"bool": {
"must_not": {
"exists" : { "field" : "tags" }
}
}
}
}
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。