elasticsearch 处理空值

考虑前面的例子,其中文档有一个称为 tags 的字段。这是一个多值字段。文档可以有一个、多个或者没有标签。如果字段没有值,那么它是怎么在倒排索引中存储的呢?
这个问题真诡异,因为答案是,它并没有被存储。让我们看一下上一节提到的倒排索引:

TokenDocIDs
open_source2
search1,2
如何存储一个在那个数据结构中不存在的字段呢?压根不行!倒排索引是一系列 token 和包含它的文档的列表。如果字段不存在,那也不会保存任何 token,所以在倒排索引中也不会有任何表示。
最终,这意味着 null、[] 和 都是等价的。都不存在于倒排索引中!
很显然,真实世界没有这么简单,数据的字段经常会丢失或者包含显式的 null 或者空数组。为了解决这些问题,elasticsearch 有一些工具来处理空值或者丢失的数据。
exists Filterexists 过滤器是第一件武器。这个过滤器返回特定字段中拥有任何值的文档。让我们使用 tagging 的例子,索引几个样本文档:
POST /my_index/posts/_bulk{ “index”: { “_id”: “1” }}{ “tags” : } …(1){ “index”: { “_id”: “2” }}{ “tags” : } …(2){ “index”: { “_id”: “3” }}{ “other_field” : “some data” } …(3){ “index”: { “_id”: “4” }}{ “tags” : null } …(4){ “index”: { “_id”: “5” }}{ “tags” : } …(5)
(1) tags 字段有一个值
(2) tags 字段有两个值
(3) tags 字段丢失
(4) tags 字段设置为 null
(5) tag 字段有一个值和一个 null
最终的倒排索引就是:

TokenDocIDs
open_source2
search1,2,5
我们的目标是找到设置了 tag 的所有文档。不管是 tag 是什么,只要它出现在文档中。在 SQL 中,我们通常可以使用 IS NOT NULL 查询:
SELECT tagsFROM postsWHERE tags IS NOT NULL在 elasticsearch 中,我们就使用 exists 过滤器:
GET /my_index/posts/_search{ “query” : { “filtered” : { “filter” : { “exists” : { “field” : “tags” }}}}}最后返回三个文档:
“hits” : [ { “_id” : “1”, “_score” : 1.0, “_source” : { “tags” : } }, { “_id” : “5”, “_score” : 1.0, “_source” : { “tags” : } …(1) }, { “_id” : “2”, “_score” : 1.0, “_source” : { “tags” : } }]
(1) 文档 5 即使包含 null 值也返回了。因为真实值的 tag 被索引了,所以这个字段存在。所以 null 对过滤器没有影响。
结果很容易理解。任何在 tags 字段中有 term 的文档都作为命中结果返回了。被排除在外的两个文档就是 3 和 4。
missing 过滤器missing 过滤器本质上是 exists 的逆:它返回对应一个特定的字段没有任何值的文档,就像 SQL:
SELECT tagsFROM postsWHERE tags IS NULL让我们用 missing 来替换上面例子中 exists 过滤器:
GET /my_index/posts/_search{ “query” : { “filtered” : { “filter”: { “missing” : { “field” : “tags” } } } }}正如你所期望的,我们拿到了在 tags 字段上没有真实值的文档——文档 3 和 4:
“hits” : 在 null 表示 null 时有时候你需要能够区分字段没有值和字段被显式地设置为 null。根据我们前面看到的默认行为,这是不可能的;数据丢失了。幸运的是,还有一种方法我们可以用一个占位符来替换显式的 null。
当指定一个 string、numeric、Boolean 或者日期字段时,你同样能设置 null_value 可以用在任何遇到显式的 null 值的地方。没有一个值的字段显然可以从倒排索引中排除。
选择合适的 null_value,确保下面的事项:

匹配了字段的类型(type)。你不能在一个类型为 date 的字段上用一个 string 的 null_value
不同于字段可能包含的正常值,来避免出令人困惑的出现 null 的真实值
exists/missing on Objectsexists/missing 过滤器同样可以用在内部对象上(inner objects),不仅仅核心类型(core types)。假如有下面的文档
{ “name” : { “first” : “John”, “last” : “Smith” }}你可以检查 name.first 和 name.last 不仅仅是 name 的存在。然而,在https://www.elastic.co/guide/en/elasticsearch/guide/current/mapping.html中,我们提到对象在内部会进行平化展开成一个简单的字段值结构,像这样:
{ “name.first” : “John”, “name.last” : “Smith”}所以,我们如何在 name 字段上使用 exists 和 missing 过滤器,这实际上并不存在于倒排索引中?
其原因就是,这会按照如下的过滤器那样:
{ “exists” : { “field” : “name” }}实际上是按照:
{ “bool”: { “should”: }}执行的。
这样也意味着如果 first 和 last 同时是空,name 命名空间就不会存在。

文/Not_GOD(简书作者)
原文链接:http://www.jianshu.com/p/7a5d706336f1
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。