龙空技术网

如何在 ES 中实现嵌套json对象查询,一次讲明白!

猿生jaf 60

前言:

眼前同学们对“mysql如何更新json嵌套的json数据”可能比较珍视,咱们都想要分析一些“mysql如何更新json嵌套的json数据”的相关知识。那么小编也在网上汇集了一些关于“mysql如何更新json嵌套的json数据””的相关内容,希望大家能喜欢,兄弟们快快来学习一下吧!

项目需求

实际的软件项目开发过程中,因为业务上的需要,我们的数据库表与表之间的结构是一对多的关系,以订单表中的数据为例,在 mysql 数据库里面,他们的关系如下图:

如果是在mysql中查询,完全可以使用join语句来实现;而ES本身就是平铺的NoSQL,对于这种1:N的关联关系应该如何来实现呢

需求落地

比较常用的实践方案,有以下三种:

● 嵌套对象

● 嵌套文档

● 父子文档

嵌套对象

先看一下像什么样子

{        "order_id":{        "type":"keyword"    },    "cancel_time":{        "type":"date",        "format":"yyyy-MM-dd HH:mm:ss"    },    "order_status":{        "type":"keyword"    },    "completed_time":{        "type":"date",        "format":"yyyy-MM-dd HH:mm:ss"    },    "create_time":{        "type":"date",        "format":"yyyy-MM-dd HH:mm:ss"    },    "is_delete":{        "type":"keyword"    },    "modify_time":{        "type":"date",        "format":"yyyy-MM-dd HH:mm:ss"    },    "order_user":{        "properties":{            "name":{                "type":"text",                "analyzer":"ik_max_word"            },            "tel":{                "type":"text",                "analyzer":"ik_max_word"            },            "user_id":{                "type":"keyword"            }        }    }}

那嵌套对象在ES中如何筛选呢,请看示例

POST order_index/_search{    "query":{        "bool":{            "must":[                {                    "match":{                        "order_user.name":"张三"                    }                },                {                    "match":{                        "order_user.tel":"18866668888"                    }                }            ]        }    }}

优点:查询时不涉及join,查询效率很高

缺点:由于json对象数组的处理是压扁了处理的,存储成扁平化的键值对列表,因此缺失关联关系。

嵌套文档

先看一下像什么样子

{    "address_list":{        "type":"nested",        "properties":{            "city_id":{                "type":"keyword"            },            "city_name":{                "type":"text",                "analyzer":"ik_max_word"            }            "district_id":{                "type":"keyword"            },            "district_name":{                "type":"text",                "analyzer":"ik_max_word"            },            "lat":{                "type":"geo_point"            },              "lon":{                "type":"geo_point"            },            "name":{                "type":"text",                "analyzer":"ik_max_word"            },            "province_id":{                "type":"keyword"            },            "province_name":{                "type":"text",                "analyzer":"ik_max_word"            }        }    }

那嵌套对象在ES中如何筛选呢,请看示例

POST order_index/_search{    "query":{        "nested":{            "path":"address_list",            "query":{                "bool":{                    "must":[                        {                            "match":{                                "address_list.province_name":"上海市"                            }                        },                        {                            "match":{                                "address_list.district_name":"浦东新区"                            }                        }                    ]                }            }        }    }}
优点:嵌套文档将实体关系嵌套组合在单文档内部(类似与json的一对多层级结构)缺点:nested子文档在ES内部是独立的文档,会造成数据量剧增,会影响查询性能。更新主文档的时候要全部更新,影响写的性能。不支持子文档从属多个主文档的场景。父子文档

先看一下像什么样子

{    "mappings":{        "_doc":{            "properties":{                "parent_id":{                    "type":"keyword"                },                "parent_join_child":{                    "type":"join",                    "relations":{                        "question":"answer"                    }                }            }        }    }}

parent_id是自定义的字段,parent_join_child是给我们的父子文档关系的名字,这个可以自定义,join表示这是一个父子文档关系,relations里面表示question是父,answer是子

首先我们插入两个父文档。

PUT exam_index/_doc/1{    "parent_id":"1",    "text":"这是一个问题1",    "parent_join_child":{        "name":"question"    }}PUT exam_index/_doc/2{    "parent_id":"2",    "text":"这是一个问题2",    "parent_join_child":{        "name":"question"    }}

其中"name":"question"表示插入的是父文档。

然后插入两个子文档

PUT exam_index/_doc/3?routing=1{    "parent_id":"3",    "text":"这是一个回答1,对应问题1",    "parent_join_child":{        "name":"answer",        "parent":"1"    }}PUT exam_index/_doc/4?routing=1{    "parent_id":"4",    "text":"这是一个回答2,对应问题1",    "parent_join_child":{        "name":"answer",        "parent":"1"    }}

子文档要解释的东西比较多,首先从文档id我们可以判断子文档都是独立的文档(跟nested不一样)。其次routing关键字指明了路由的id是父文档1, 这个id和下面的parent关键字对应的id是一致的。需要强调的是,索引子文档的时候,routing是必须的,因为要确保子文档和父文档在同一个分片上。"name":"answer"关键字指明了这是一个子文档。

特点:父子文档类似关系型数据库中的关联关系,适用于写多的场景

现在exam_index索引中有四个独立的文档,我们来看父子文档在搜索的时候是什么姿势。

如果我们想通过子文档信息,查询父文档,可以通过如下方式实现:

POST exam_index/_search{    "query":{        "has_child":{            "type":"answer",            "query":{                "match":{                    "text":"回答"                }            }        }    }}

返回结果:

[    {        "_index":"exam_index",        "_type":"_doc",        "_id":"1",        "_score":1,        "_source":{            "my_id":"1",            "text":"这是一个问题1",            "parent_join_child":{                "name":"question"            }        }    }]

如果我们想通过父文档信息,查询子文档,可以通过如下方式实现:

POST exam_index/_search{    "query":{        "has_parent":{            "parent_type":"question",            "query":{                "match":{                    "text":"问题"                }            }        }    }}

返回结果:

[    {        "_index":"crm_exam_index",        "_type":"_doc",        "_id":"3",        "_score":1,        "_routing":"1",        "_source":{            "my_id":"3",            "text":"这是一个回答1,对应问题1",            "parent_join_child":{                "name":"answer",                "parent":"1"            }        }    },    {        "_index":"crm_exam_index",        "_type":"_doc",        "_id":"4",        "_score":1,        "_routing":"1",        "_source":{            "my_id":"4",            "text":"这是一个回答2,对应问题1",            "parent_join_child":{                "name":"answer",                "parent":"1"            }        }    }]

如果我们想通过父 ID 查询子文档,可以通过如下方式实现:

POST exam_index/_search{    "query":{        "parent_id":{            "type":"answer",            "id":"1"        }    }}

返回结果和上面一样,区别在于parent_id搜索默认使用相关性算分,而has_parent默认情况下不使用算分。

使用父子文档的模式有一些需要特别关注的点:

● 每一个索引只能定义一个join field

● 父子文档必须在同一个分片上,意味着查询,更新操作都需要加上routing

● 可以向一个已经存在的join field上新增关系

● 父子文档,适合那种数据结构基本一致的场景,如果两个表结构完全不一致,不建议使用这种结构

● 父子文档也有缺点,查询速度是这三个方案里面最慢的一个

标签: #mysql如何更新json嵌套的json数据 #mysql语句嵌套查询