文章目录
1、概述
本文主要解决在 ES 中如何处理空只或者 NULL 值,如检索值为空的文档,如何存储空值或 NULL 值等。
2、使用 null_value 处理空值
3、使用 exists 函数查询值为空的文档
3.1 使用场景
在实际工作中,不可避免的可能会遇到以下需求:
查询 xx 字段 value = "" and value = null
的文档,或者value != "" and value != null
的文档
3.2 ES 中常见的空值查询方式
在本文第二小节相关文章的描述中,在 ES 中对于空值字段,通常采取的处理方式有两种:
- 空值替换:预先对空值字段采取替换,即使用 null_value 设置针对于空值字段的替换值,具体用法详见文章内:传送门
- exists 函数:exists 函数可用以判断字段是否存在,特定场景下可以用于对空值字段的查询
3.3 常见误区
我们仍以下面案例作为示例数据,来演示 esists 的具体使用细节
DELETE null_value_index
PUT null_value_index
{
"mappings": {
"properties": {
"null_field": {
"type": "text",
"fields": {
"keyword":{
"type": "keyword"
}
}
}
}
}
}
# 写入测试数据
PUT null_value_index/_bulk
{"index":{"_id":1}}
{"null_field":null}
{"index":{"_id":2}}
{"null_field":"null"}
{"index":{"_id":3}}
{"null_field":""}
{"index":{"_id":4}}
{"null_field":" "}
{"index":{"_id":5}}
{"null_field":[]}
对于上述示例索引中的数据,如果我们希望 查询 null_field 字段不为空的所有文档,读者可以思考一下如何实现:
常见的误区:
以下是最容易产生的错误答案:
# 【错误答案】思路是查询字段值为""的文档
GET null_value_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"null_field": ""
}
}
]
}
}
}
# 或者:
# 【错误答案】思路是查询字段 null_field != "" 的文档
GET null_value_index/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"null_field": ""
}
}
]
}
}
}
执行以上查询发现结果是空的
3.4 使用 bool 查询函数查询空值字段
对于使用 must
或者 must_not
查询空值字段的方式是否可行呢?
虽然 3.3 小节中的答案是错误的,但是如果在此基础上稍加改变,将 match 查询改为 term 查询,就会得到争取的结果:
GET null_value_index/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"null_field.keyword": ""
}
}
]
}
}
}
执行结果如下(注意字段从 null_field
改为了 null_field.keyword
):
对于上述结果,不难发现其扔存在一定问题:对于此类需求,往往需要查找的是所有空值
字段,包括 ""、空白符、" "、null
等。解决办法也很简单:
把 term 换成 terms 即可
GET null_value_index/_search
{
"query": {
"bool": {
"must": [
{
"terms": {
"null_field.keyword": [
"",
" "
]
}
}
]
}
}
}
3.5 exists 函数详解
3.5.1 bool 查询的不足
对于使用 bool 查询的方式查询空值字段是否能完美解决问题呢?
答案是:不能! id: 1
的这条数据并未被查询到,
肯定有大聪明会说,那是因为 terms 查询中并未添加 null
,那么好,我们加上,发现会出现异常:
请注意,这里是 null
而非字符串 "null"
。
3.5.3 exists 的基本使用
那么对于以上问题,就需要使用 exists 函数来帮助解决。
首先要注意的是,exists 函数的意义是查询某个字段是不是存在,而非字段值。
举个例子,当我们执行以下代码的时候,其语义为:查询所有不存在 un_exists_field 字段的文档
GET null_value_index/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "un_exists_field"
}
}
]
}
}
}
当我们使用 exists 去查询 null 会出现什么结果呢?
请注意看,结果中包含了两条数据,被 exists 查询的字段都是存在的,但是却被召回了,这意味着:值为 null 或者 空数组 的字段,会被当做这个字段是不存在的
3.6 完美方案
基于 exists 这个特性,就弥补了 bool 查询不能查到 null 和 [] 这两种情况的缺陷,那么就可以将这两个查询结合起来使用
为了方便对比结果,我们加入第六条测试数据:
PUT null_value_index/_doc/6
{
"null_field":"只是唯一有值的记录"
}
然后执行测试代码
# 查询所有空值字段, 包括 null、 ""、空白符、空数组等
# 第一个子查询匹配 null 和 空数组,第二个子查询匹配其他空白符和 ""。
GET null_value_index/_search
{
"query": {
"bool": {
"should": [
{
"bool": {
"must_not": [
{
"exists": {
"field": "null_field"
}
}
]
}
},
{
"terms": {
"null_field.keyword": [
"",
" "
]
}
}
]
}
}
}
执行结果如图:
字段值为“空值”的 id:1/3/4/5 四条记录都被召回了,而 id:2/6 因为字段值不为空或 null,因此没有被召回,虽然 id: 2 的值为 “null”,但其也只不过为一个值为 “null” 的字符串而已,如果不希望被召回,将其添加到 terms 的 value 数组中即可。
同理,如果我们想 查询值不为空
的所有记录,将上述代码改为以下代码即可
# 查询所有非空值字段
# 第一个子查询,匹配所有值非 null 和 空数组 的记录
# 第二个子查询,在第一个基础上,过滤掉所有值为 ""、" " 的字段
GET null_value_index/_search
{
"query": {
"bool": {
"must":[
{
"exists": {
"field": "null_field"
}
},
{
"bool": {
"must_not": [
{
"terms": {
"null_field.keyword": [
"",
" "
]
}
}
]
}
}
]
}
}
}
执行结果如下:
同理,如果不希望 “null” 被查询到,将其添加到第二个子查询即可。
推荐阅读:【开源社区】Elasticsearch(ES)中空值字段 null_value 及通过exists查找非空文档