0%

elasticsearch连接查询

Elasticsearch 连接查询:嵌套查询与父子查询详解

在 Elasticsearch 中,处理关联数据(如一对多关系)时,传统的对象类型(object)会因内部扁平化存储导致查询异常。为此,ES 提供了两种专门的关联查询方式:嵌套查询(nested query)父子查询(parent-child query),分别适用于不同的业务场景。

对象类型的局限性:为何需要连接查询?

对象类型(object)用于存储 JSON 对象数组,但 ES 会在内部将其扁平化处理,导致对象边界丢失,从而引发查询逻辑错误。

问题示例

假设有一篇包含多个事件的文档:

1
2
3
4
5
6
7
8
{
"name": "技术大会",
"events": [
{ "date": "2022-10-10", "title": "java架构大会" },
{ "date": "2022-08-08", "title": "hadoop大会" },
{ "date": "2022-02-02", "title": "elasticsearch大会" }
]
}

ES 内部会将其扁平化为:

1
2
3
4
5
{
"name": "技术大会",
"events.date": ["2022-10-10", "2022-08-08", "2022-02-02"],
"events.title": ["java架构大会", "hadoop大会", "elasticsearch大会"]
}

此时查询 “title:hadoopdate 在 2022-10-01 至 2022-12-31 之间” 的事件,会错误匹配:

  • 条件 title:hadoop 匹配第二个事件(date:2022-08-08);
  • 条件 date 范围匹配第一个事件(date:2022-10-10);
  • 扁平化存储导致 ES 认为 “存在同时满足两个条件的文档”,返回错误结果。

结论

对象类型仅适用于一对一关系,对于一对多关系,需使用嵌套查询或父子查询。

嵌套查询(Nested Query)

嵌套查询通过 nested 类型将对象数组存储为独立的 Lucene 文档,保留对象边界,确保多字段条件能正确关联到同一对象。

定义嵌套类型映射

需在映射中显式声明字段为 nested 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PUT conference
{
"mappings": {
"default": {
"properties": {
"name": { "type": "text" },
"events": {
"type": "nested", // 声明为嵌套类型
"properties": {
"date": { "type": "date" },
"title": { "type": "text" }
}
}
}
}
}
}

插入嵌套文档

与普通对象数组格式一致,ES 会自动将 events 中的每个对象作为独立 Lucene 文档存储:

1
2
3
4
5
6
7
8
9
PUT conference/default/1
{
"name": "技术大会",
"events": [
{ "date": "2022-10-10", "title": "java架构大会" },
{ "date": "2022-08-08", "title": "hadoop大会" },
{ "date": "2022-02-02", "title": "elasticsearch大会" }
]
}

执行嵌套查询

使用 nested 子句指定嵌套路径(path)和查询条件,确保条件仅匹配同一嵌套对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET conference/_search
{
"query": {
"nested": {
"path": "events", // 嵌套对象的路径
"query": {
"bool": {
"must": [
{ "term": { "events.title": "hadoop" } }, // 条件1:标题含hadoop
{ "range": { "events.date": { "gte": "2022-10-01", "lte": "2022-12-31" } } } // 条件2:日期在范围内
]
}
}
}
}
}
结果

该查询返回空结果(正确),因为 hadoop 事件的日期不在范围内。

嵌套查询的参数

  • path:嵌套对象在文档中的路径(如 events)。
  • query:作用于嵌套对象的查询条件(内部字段需用全路径,如 events.title)。
  • score_mode:嵌套对象的匹配得分如何影响父文档得分(默认 avg,可选 sum/max/min/none)。

优缺点与适用场景

优点 缺点 适用场景
保留对象边界,查询准确; 查询性能好(嵌套对象与父文档同分片); 支持聚合操作。 更新任一嵌套对象需重新索引整个文档; 嵌套层级过深可能影响性能。 嵌套对象不频繁更新的场景(如商品的规格参数、文章的评论)。

父子查询(Parent-Child Query)

父子查询通过 join 类型定义文档间的父子关系,父文档和子文档作为独立 ES 文档存储,更新子文档无需影响父文档,适合频繁更新的场景。

定义父子关系映射(6.x+)

6.x 后通过 join 类型替代旧版 _parent 字段,显式声明父子关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT Q&A  // 索引名
{
"mappings": {
"default": {
"properties": {
"my_join_field": { // 用于定义父子关系的字段
"type": "join",
"relations": {
"question": "answer" // question为父类型,answer为子类型
}
}
}
}
}
}

插入父文档与子文档

  • 父文档:需指定 join 字段的名称为父类型:

    1
    2
    3
    4
    5
    PUT Q&A/default/1?refresh  // 父文档ID=1
    {
    "text": "什么是Elasticsearch?",
    "my_join_field": { "name": "question" } // 声明为父类型
    }
  • 子文档:需通过 routing 参数确保与父文档同分片,并指定 parent 为父文档 ID:

    1
    2
    3
    4
    5
    6
    7
    8
    PUT Q&A/default/101?routing=1&refresh  // 子文档ID=101,routing=父文档ID
    {
    "text": "Elasticsearch是一款搜索引擎",
    "my_join_field": {
    "name": "answer", // 声明为子类型
    "parent": "1" // 关联父文档ID=1
    }
    }

    注意:routing 确保父子文档存储在同一分片,否则无法查询关联关系。

常用父子查询

(1)has_child:通过子文档查询父文档

返回包含匹配子文档的父文档:

1
2
3
4
5
6
7
8
9
GET Q&A/_search
{
"query": {
"has_child": {
"type": "answer", // 子类型名
"query": { "term": { "text": "搜索引擎" } } // 子文档条件
}
}
}
  • 结果:返回父文档(ID=1),因为其包含匹配 “搜索引擎” 的子文档。
(2)has_parent:通过父文档查询子文档

返回关联匹配父文档的子文档:

1
2
3
4
5
6
7
8
9
GET Q&A/_search
{
"query": {
"has_parent": {
"parent_type": "question", // 父类型名
"query": { "term": { "text": "elasticsearch" } } // 父文档条件
}
}
}
  • 结果:返回子文档(ID=101),因为其关联的父文档包含 “elasticsearch”。
(3)parent_id:通过父 ID 查询子文档

返回指定父文档的所有子文档:

1
2
3
4
5
6
7
8
9
GET Q&A/_search
{
"query": {
"parent_id": {
"type": "answer", // 子类型名
"id": "1" // 父文档ID
}
}
}
  • 结果:返回父文档 ID=1 的所有子文档(如 ID=101)。

优缺点与适用场景

优点 缺点 适用场景
子文档更新不影响父文档; 支持频繁更新的子文档。 查询性能低于嵌套查询(需跨文档匹配); 父子文档必须同分片(依赖 routing)。 子文档频繁更新的场景(如问答的回复、订单的物流记录)。

嵌套查询 vs 父子查询:如何选择?

维度 嵌套查询(Nested) 父子查询(Parent-Child)
存储方式 嵌套对象与父文档同 Lucene 文档 父子文档为独立 ES 文档
更新成本 更新子对象需重新索引整个文档 子文档更新不影响父文档
查询性能 高(同分片,无跨文档查找) 中(需关联独立文档)
适用场景 子对象不频繁更新(如商品属性) 子对象频繁更新(如评论、日志)

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10