1、引言
实战业务场景中,经常会遇到定义空值、检索指定空值数据的情况。
这时候,当我们翻看官方文档 null_value 部分,会看到如下的描述:
Accepts a string value which is substituted for any explicit null values. Defaults to null, which means the field is treated as missing.
接受一个字符串值替换所有显式的空值。默认为null,这意味着该字段被视为丢失。
A null value cannot be indexed or searched. When a field is set to null, (or an empty array or an array of null values) it is treated as though that field has no values.
空值不能被索引或搜索。当字段设置为null(或空数组或 null 值的数组)时,将其视为该字段没有值。
光看字面意思,你是不是感觉不好理解?
好吧,死磕一把,探个究竟:
代码语言:javascript复制DELETE my-index-000001
PUT my-index-000001
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
}
}
PUT my-index-000001/_bulk
{"index":{"_id":1}}
{"status_code":null,"title":"just test"}
{"index":{"_id":2}}
{"status_code":"","title":"just test"}
{"index":{"_id":3}}
{"status_code":[],"title":"just test"}
POST my-index-000001/_search
POST my-index-000001/_search
{
"query": {
"term": {
"status_code": null
}
}
}
如上检索返回错误如下:
代码语言:javascript复制{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "field name is null or empty"
}
],
"type": "illegal_argument_exception",
"reason": "field name is null or empty"
},
"status": 400
}
2、null_value 的含义
The null_value parameter allows you to replace explicit null values with the specified value so that it can be indexed and searched.
使用 null_value 参数可以用指定的值替换显式的空值,以便可以对其进行索引和搜索。例如:
代码语言:javascript复制DELETE my-index-000001
PUT my-index-000001
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword",
"null_value": "NULL"
}
}
}
}
PUT my-index-000001/_bulk
{"index":{"_id":1}}
{"status_code":null}
{"index":{"_id":2}}
{"status_code":[]}
{"index":{"_id":3}}
{"status_code":"NULL"}
GET my-index-000001/_search
{
"query": {
"term": {
"status_code": "NULL"
}
}
}
注意,这里返回结果:包含 _id = 1 以及 _id =3 的文档,不包含_id = 2 的文档。
说明一下:
"null_value": "NULL" 的含义:用指定的值替换显式的空值,“NULL”可以自定义,比如业务系统中我们可以定义成“Unkown”。
大家能看懂的大白话解释如下:
- 相当于我们在 Mapping 定义阶段指定了空的默认值,用“NULL”来代替,这样做的好处:类似如上的_id = 1 的文档,空字段也可以被索引、检索。
- 不会再报 "field name is null or empty" 的错误了。
3、null_value 使用注意
- null_value 必须和定义的数据类型匹配,举例:long 类型字段不能有string 类型的 null value。
如下的定义就会报错:
代码语言:javascript复制PUT my-index-000001
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword"
},
"title": {
"type": "long",
"null_value": "NULL"
}
}
}
}
报错如下:
代码语言:javascript复制{
"error": {
"root_cause": [
{
"type": "mapper_parsing_exception",
"reason": "Failed to parse mapping [_doc]: For input string: "NULL""
}
],
"type": "mapper_parsing_exception",
"reason": "Failed to parse mapping [_doc]: For input string: "NULL"",
"caused_by": {
"type": "number_format_exception",
"reason": "For input string: "NULL""
}
},
"status": 400
}
解释一下:明显类型不匹配导致。
- null_value 只影响了数据的索引,不会修改_source 文档。
4、哪些字段有null_value, 哪些字段没有null_value?
以下核心的常用字段都支持:null_value。
- Arrays
- Boolean
- Date
- geo_point
- IP
- Keyword
- Numeric
- point
别问我怎么知道的,挨个翻查的官方文档确认的。
最核心常问到的问题:
4.1 问题1:text 类型不支持 null_value 吗?
是的,不支持。
来吧,实战一把:
代码语言:javascript复制DELETE my-index-000001
PUT my-index-000001
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword"
},
"title": {
"type": "text",
"null_value": "NULL"
}
}
}
}
返回结果如下:
代码语言:javascript复制{
"error": {
"root_cause": [
{
"type": "mapper_parsing_exception",
"reason": "Mapping definition for [title] has unsupported parameters: [null_value : NULL]"
}
],
"type": "mapper_parsing_exception",
"reason": "Failed to parse mapping [_doc]: Mapping definition for [title] has unsupported parameters: [null_value : NULL]",
"caused_by": {
"type": "mapper_parsing_exception",
"reason": "Mapping definition for [title] has unsupported parameters: [null_value : NULL]"
}
},
"status": 400
}
问题2:如果 text 类型也想设置空值,怎么搞呢?
推荐 multi-fields,借助 keyword 和 text 组合达到业务需求。
定义参考如下:
代码语言:javascript复制PUT my-index-000001
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"null_value": "NULL"
}
}
}
}
}
}
对于 text 类型的字段,实战业务场景下,我们经常会需要同时设置:multi_fields, 将 text 和 keyword 组合设置。
text 类型用于全文检索,keyword用于聚合和排序。
同时,multi_fields 是 Elastic 认证工程师的核心考点之一,大家务必要掌握。
5、线上问题探讨
老哥们,请教一个问题 ,我现在数据中有content这个字段,我想查询这个字段不为空字符串,我用must_not不行。我贴下我的sql 死磕 Elasticsearch 技术交流群
我的解读如下:
说下这个问题正确写法,以及之前写法不对的原因。
判定是否为空,本质是:精准匹配问题,不是全文检索的范畴(相似度匹配),所以选型使用:match_phrase 导致后面的错误。应该使用:term。
代码语言:javascript复制POST test_001/_search
{
"query": {
"bool": {
"filter": {
"bool": {
"must": [
{
"exists": {
"field": "cont"
}
},
{
"term": {
"content.keyword": {
"value": ""
}
}
}
]
}
}
}
}
}
注意:exists 检索的含义是判定字段是否存在,结合使用效果佳、更稳妥!
如下的脚本也可以实现,但由于性能问题,实际业务层面不推荐使用。
代码语言:javascript复制POST test_001/_search
{
"query": {
"bool": {
"filter": {
"script": {
"script": {
"source": "doc['content.keyword'].length == 1",
"lang": "painless"
}
}
}
}
}
}
试想一下,如果在定义 Mapping 的数据建模阶段就定义了 text 和 keyword的组合 fields,并且:为keyword 设置了 null_value,这个问题就更好解决了。
6、小结
正如罗胖所说:再显而易见的道理,在中国至少有一亿人不知道。
而,我认为 Elasticsearch 技术也是,再显而易见的技术点,在中国 Elastic 技术圈,至少有 N 多人不知道。
怎么办?死磕一下,探个究竟吧!
你的业务场景怎么处理空值的呢?欢迎留言讨论。
7、加餐-讨论
有读者私信留言:
其实老大也可以学习一下其他号,转载一些大 V 或大公司的流量文章,图文并茂的,虽然大家不一定看得懂,但看上去就很牛逼的样子,给你的公号也能增粉,多好啊!你一周才1篇文章,大家都走了。
我的回复:
每个公众号都有自己的使命和存在的价值,你可以仔细对比,每个号主都有自己的特点,为什么要趋同呢?我感觉这样也很好(就是苦点、穷点),并且从长远角度(十年甚至更长时间),坚持原创的价值才能体现。
加微信:elastic6(仅有少量坑位了),和 BAT 大佬一起精进 Elastic 技术!