2026/8/2 9:27:11

第三阶段 25 · nested 嵌套对象与查询(数组对象最大的坑)

第三阶段 25 · nested 嵌套对象与查询(数组对象最大的坑) 25 · nested 嵌套对象与查询数组对象最大的坑阶段第三阶段 / 查询 聚合ESnested类型 nestedquery / agg | PostgreSQL一对多子表 JOIN / JSONB 数组1. 概念为什么普通对象数组会“串味”ES 默认把对象数组扁平化存储。假设一个订单里有多个明细{order_no:SO-1,items:[{sku:A,qty:10},{sku:B,qty:99}]}默认object类型ES 内部会拆成items.sku [A, B]、items.qty [10, 99]——字段间的对应关系丢了。于是你查「skuA 且 qty99」时会错误命中这条文档因为 A 在 sku 列里、99 在 qty 列里跨对象串味了。解决办法把items声明为nested。nested 会把数组里每个对象作为独立的隐藏子文档索引字段对应关系得以保留——概念上就像一张一对多子表。2. PostgreSQL 对照-- nested 约等于一对多子表 JOINSELECTo.*FROMorders oJOINorder_items iONi.order_noo.order_noWHEREi.skuAANDi.qty99;-- 条件作用在“同一行明细”上关键点nestedquery 的语义就是「同一个子对象内同时满足多个条件」对应 SQL 里条件落在 JOIN 后的同一行。3. ES DSL3.1 定义 nested 字段mappingPUT orders_idx { mappings: { properties: { order_no: { type: keyword }, items: { type: nested, properties: { sku: { type: keyword }, qty: { type: integer } } } } } }3.2 nested 查询同一子对象内满足多条件GET orders_idx/_search { query: { nested: { path: items, query: { bool: { filter: [ { term: { items.sku: A } }, { term: { items.qty: 99 } } ] } }, inner_hits: {} // 可选回传到底是哪个子对象命中的 } } }没有nested包裹时上面这组条件会串味误命中包了nested就只命中「同一条明细里 skuA 且 qty99」。3.3 nested 聚合先进子文档再聚合GET orders_idx/_search { size: 0, aggs: { items_agg: { nested: { path: items }, aggs: { by_sku: { terms: { field: items.sku }, aggs: { total_qty: { sum: { field: items.qty } } } } } } } }4. Spring Boot 实现ComponentpublicclassDoc25NestedQuery{AutowiredprivateElasticsearchClientelasticsearchClient;/** 同一条明细里 sku? 且 qty?并回传命中的子对象 */publicListMapString,ObjectbyItem(StringindexName,Stringsku,intqty)throwsIOException{SearchResponseMaprespelasticsearchClient.search(s-s.index(indexName).query(q-q.nested(n-n.path(items).query(nq-nq.bool(b-b.filter(f-f.term(t-t.field(items.sku).value(sku))).filter(f-f.term(t-t.field(items.qty).value(qty))))).innerHits(ih-ih))),// inner_hits拿到具体命中的子对象Map.class);returnresp.hits().hits().stream().map(Hit::source).filter(Objects::nonNull).collect(Collectors.toList());}}nested的path必须与 mapping 里的 nested 字段名一致子字段用「path.子字段」引用。inner_hits拿命中的子对象hit.innerHits().get(items).hits().hits()。5. 坑与最佳实践必须先在 mapping 里声明nested类型一旦建好不能从object原地改成nested需 reindex第 33 篇。查询/聚合都要用nested包裹直接term(items.sku)不会串味报错但语义错跨对象命中。nested 有成本每个子对象是独立 Lucene 文档数量大时占空间、变慢用index.mapping.nested_objects.limit兜底。数组内对象少而稳定才适合 nested关系复杂、需独立更新子项考虑 joinparent-child代价更高。inner_hits才能知道“哪条子对象命中”做高亮/回显时很有用。需要「独立文档的父子关系 / 跨文档关联 / 什么时候该反范式」见第 51 篇《join 父子关系与关系建模》。下一篇26-highlight-高亮.md。← 上一篇24-filter-bucket聚合 总览 下一篇26-highlight-高亮 →