英文版地址: https://www.elastic.co/guide/en/elasticsearch/guide/current/_dealing_with_null_values.html
本书基于 Elasticsearch 2.x 版本,有些内容可能已经过时。
处理 Null 值edit
回想在之前例子中,有的文档有名为 tags
(标签)的字段,它是个多值字段,一个文档可能有一个或多个标签,也可能根本就没有标签。如果一个字段没有值,那么如何将它存入倒排索引中的呢?
这是个有欺骗性的问题,因为答案是:什么都不存。让我们看看之前内容里提到过的倒排索引:
Token |
DocIDs |
|
|
|
|
如何将某个不存在的字段存储在这个数据结构中呢?无法做到!简单的说,一个倒排索引只是一个 token 列表和与之相关的文档信息,如果字段不存在,那么它也不会持有任何 token,也就无法在倒排索引结构中表现。
最终,这也就意味着,null
, []
(空数组)和 [null]
所有这些都是等价的,它们无法存于倒排索引中。
显然,世界并不简单,数据往往会有缺失字段,或有显式的空值或空数组。为了应对这些状况,Elasticsearch 提供了一些工具来处理空或缺失值。
存在查询edit
第一件武器就是 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] }
以上文档集合中 tags
字段对应的倒排索引如下:
Token |
DocIDs |
|
|
|
|
我们的目标是找到那些被设置过标签字段的文档,并不关心标签的具体内容。只要它存在于文档中即可,用 SQL 的话就是用 IS NOT NULL
非空进行查询:
SELECT tags FROM posts WHERE tags IS NOT NULL
在 Elasticsearch 中,使用 exists
查询的方式如下:
GET /my_index/posts/_search { "query" : { "constant_score" : { "filter" : { "exists" : { "field" : "tags" } } } } }
这个查询返回 3 个文档:
"hits" : [ { "_id" : "1", "_score" : 1.0, "_source" : { "tags" : ["search"] } }, { "_id" : "5", "_score" : 1.0, "_source" : { "tags" : ["search", null] } }, { "_id" : "2", "_score" : 1.0, "_source" : { "tags" : ["search", "open source"] } } ]
显而易见,只要 tags
字段存在项(term)的文档都会命中并作为结果返回,只有 3 和 4 两个文档被排除。
缺失查询edit
这个 missing
查询本质上与 exists
恰好相反:它返回某个特定 无 值字段的文档,与以下 SQL 表达的意思类似:
SELECT tags FROM posts WHERE tags IS NULL
我们将前面例子中 exists
查询换成 missing
查询:
GET /my_index/posts/_search { "query" : { "constant_score" : { "filter": { "missing" : { "field" : "tags" } } } } }
按照期望的那样,我们得到 3 和 4 两个文档(这两个文档的 tags
字段没有实际值):
"hits" : [ { "_id" : "3", "_score" : 1.0, "_source" : { "other_field" : "some data" } }, { "_id" : "4", "_score" : 1.0, "_source" : { "tags" : null } } ]
对象上的存在与缺失edit
不仅可以过滤核心类型, exists
and missing
查询
还可以处理一个对象的内部字段。以下面文档为例:
{ "name" : { "first" : "John", "last" : "Smith" } }
我们不仅可以检查 name.first
和 name.last
的存在性,也可以检查 name
,不过在 映射 中,如上对象的内部是个扁平的字段与值(field-value)的简单键值结构,类似下面这样:
{ "name.first" : "John", "name.last" : "Smith" }
那么我们如何用 exists
或 missing
查询 name
字段呢? name
字段并不真实存在于倒排索引中。
原因是当我们执行下面这个过滤的时候:
{ "exists" : { "field" : "name" } }
实际执行的是:
{ "bool": { "should": [ { "exists": { "field": "name.first" }}, { "exists": { "field": "name.last" }} ] } }
这也就意味着,如果 first
和 last
都是空,那么 name
这个命名空间才会被认为不存在。