在ES的日常使用中,需要使用到Nested结构存储数个同级的子节点数据,例如一条主订单下的N条子订单的数据。

新增更新操作

现在,假设我们在ES中有这样一条数据

PUT /celebrities/_doc/114
{
    "user" : "Kun",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "Idol who has been practicing for two and a half years",
    "skills":[
        {
            "name":"sing",
            "skill_level":"A"
        },
        {
            "name":"jump",
            "skill_level":"S"
        },
        {
            "name":"rap",
            "skill_level":"SS"
        }
    ]
}

我们需要往skills的Nested数组中添加一个新的节点,节点的name为“consecutive five whips”,则可以这么写

POST /celebrities/_doc/114
{
    "script": {
        "source": "if (ctx._source.skills == null) {List ls = new ArrayList();ls.add(params.skill);ctx._source.skills = ls;} else {ctx._source.skills.add(params.skill);}",
        "lang": "painless",
        "params": {
            "skill": {
                "name": "consecutive five whips",
                "skill_level": "SSS"
            }
        }
    }
}

得到返回结果,表明执行成功

{
    "_index": "celebrities",
    "_id": "114",
    "_version": 6,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 6,
    "_primary_term": 1
}

通过代码可以简单的看出逻辑,如果_source.skills为null,则创建一个新的ArrayList,并将参数中的skill节点的内容放进去,最后赋值给_source.skills,反之如果不为null的话,则直接往里加入当前的skill字段的内容。

if (ctx._source.skills == null) {
    List ls = new ArrayList();
    ls.add(params.skill);
    ctx._source.skills = ls;
} else {
    ctx._source.skills.add(params.skill);
}

要先判断是否为null,否则无法调用add方法,并会抛出一个异常

"caused_by": {
                "type": "null_pointer_exception",
                "reason": "cannot access method/field [add] from a null def reference"
}

上面的代码是可以正确添加新的值了,但是如果不小心重复执行的话,会一直往skills的Nested数组中重复添加相同的数据,所以接下来我们仍要做进一步的优化,进行去重判断,根据某个字段做唯一判断,有则更新,无则新增

首先我们还是将数据恢复成

"skills":[
    {
        "name":"sing",
        "skill_level":"A"
    },
    {
        "name":"jump",
        "skill_level":"S"
    },
    {
        "name":"rap",
        "skill_level":"SS"
    }
]

之后执行我们修改后的update语句

POST /celebrities/_update/114
{
    "script": {
        "source": "if (ctx._source.skills == null) {List ls = new ArrayList();ls.add(params.skill);ctx._source.skills = ls;} else { def flag = true;for (item in ctx._source.skills) { if (item['name'] == params.skill.name) { item['skill_level'] = params.skill.skill_level;flag = false;}} if(flag){ctx._source.skills.add(params.skill)}}",
        "lang": "painless",
        "params": {
            "skill": {
                "name": "consecutive five whips",
                "skill_level": "SSS"
            }
        }
    }
}

看到返回结果,更新插入成功

{
    "_index": "celebrities",
    "_id": "114",
    "_version": 7,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 15,
    "_primary_term": 1
}

根据文档ID,GET一下查询结果

GET /celebrities/_doc/114

确认无误,成功执行了插入

{
    "_index": "celebrities",
    "_id": "114",
    "_version": 7,
    "_seq_no": 15,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "user": "Kun",
        "post_date": "2009-11-15T14:12:12",
        "message": "Idol who has been practicing for two and a half years",
        "skills": [
            {
                "name": "sing",
                "skill_level": "A"
            },
            {
                "name": "jump",
                "skill_level": "S"
            },
            {
                "name": "rap",
                "skill_level": "SS"
            },
            {
                "name": "consecutive five whips",
                "skill_level": "SSS"
            }
        ]
    }
}

之后我们再修改下update语句的内容,期望能看到name为consecutive five whips的这条数据skill_level更新为SSS+,而不是skills的Nested嵌套结构中再新增一条name为consecutive five whips的数据

POST /celebrities/_update/114
{
    "script": {
        "source": "if (ctx._source.skills == null) {List ls = new ArrayList();ls.add(params.skill);ctx._source.skills = ls;} else { def flag = true;for (item in ctx._source.skills) { if (item['name'] == params.skill.name) { item['skill_level'] = params.skill.skill_level;flag = false;}} if(flag){ctx._source.skills.add(params.skill)}}",
        "lang": "painless",
        "params": {
            "skill": {
                "name": "consecutive five whips",
                "skill_level": "SSS+"
            }
        }
    }
}

执行成功后,再次查询GET /celebrities/_doc/114,得到结果,确实如我们预期的那样

{
    "_index": "celebrities",
    "_id": "114",
    "_version": 8,
    "_seq_no": 16,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "user": "Kun",
        "post_date": "2009-11-15T14:12:12",
        "message": "Idol who has been practicing for two and a half years",
        "skills": [
            {
                "name": "sing",
                "skill_level": "A"
            },
            {
                "name": "jump",
                "skill_level": "S"
            },
            {
                "name": "rap",
                "skill_level": "SS"
            },
            {
                "name": "consecutive five whips",
                "skill_level": "SSS+"
            }
        ]
    }
}

删除操作

如果需要删除某一条的话,则可以

POST /celebrities/_update/114
{
    "script": {
        "source": "ctx._source.skills.removeIf(item -> item.name == params.skill.name)",
        "lang": "painless",
        "params": {
            "skill": {
                "name": "consecutive five whips"
            }
        }
    }
}

看到更新成功,我们再次查询下GET /celebrities/_doc/114,确认确实是删除掉了

{
    "_index": "celebrities",
    "_id": "114",
    "_version": 9,
    "_seq_no": 18,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "user": "Kun",
        "post_date": "2009-11-15T14:12:12",
        "message": "Idol who has been practicing for two and a half years",
        "skills": [
            {
                "name": "sing",
                "skill_level": "A"
            },
            {
                "name": "jump",
                "skill_level": "S"
            },
            {
                "name": "rap",
                "skill_level": "SS"
            }
        ]
    }
}

上面的演示数据中 script 是 painless的语法,painless是es中对脚本支持较好的。

https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html#nested-fields-array-objects

https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html