MongoDB聚合查询嵌套数组过滤问题及数据结构优化咨询
MongoDB嵌套数组过滤与数据结构优化问题
问题背景
已实现过滤sales数组中label=1的对象,但无法进一步过滤sales下嵌套的codes(仅保留active=True)和prices(仅保留valid_from大于今日)数组,尝试的查询返回空结果;同时咨询当前数据结构是否最优,是否需要将sales拆分到单独集合通过关联实现。
数据结构
data = { "_id": "abc", "field": "value", "misc": { "height": 1.1, }, "sales": [ { "label": 1, "codes": [ { "code": 123456, "active": True }, { "code": 234567, "active": False }, { "code": 345678, "active": False }, ], "prices": [ { "price": 2.99, "valid_from": datetime(year=2023, month=3, day=1) }, { "price": 3.99, "valid_from": datetime(year=2023, month=4, day=1) }, { "price": 4.99, "valid_from": datetime(year=2023, month=5, day=1) }, { "price": 5.99, "valid_from": datetime(year=2023, month=6, day=1) }, ] }, { "label": 2, "codes": [ { "code": 987654, "active": True }, { "code": 876543, "active": False }, { "code": 765432, "active": False }, ], "prices": [ { "price": 2.99, "valid_from": datetime(year=2023, month=3, day=1) }, { "price": 3.99, "valid_from": datetime(year=2023, month=4, day=1) }, { "price": 4.99, "valid_from": datetime(year=2023, month=5, day=1) }, { "price": 6.99, "valid_from": datetime(year=2023, month=6, day=1) }, ] } ] }
已生效的聚合查询
db.MasterData.aggregate([ { "$match": { "_id": "abc" } }, { "$project": { "field": 1, "sales": { "$filter": { "input": "$sales", "as": "item", "cond": { "$eq": ["$$item.label", 1] } } } } } ])
尝试失败的聚合查询
db.MasterData.aggregate([ { "$match": { "_id": "abc" } }, { "$project": { "field": 1, "sales.codes": { "$filter": { "input": "$sales.codes", "as": "item", "cond": { "$eq": ["$$item.active", True] } } } } } ])
解决方案
一、正确的嵌套数组过滤聚合查询
失败原因是直接对sales.codes做$filter时,$sales.codes是二维数组(每个sales元素都包含独立的codes数组),无法直接用$filter处理。需先过滤sales数组,再对每个sales元素内的codes和prices分别做过滤,推荐用$map配合$filter实现:
db.MasterData.aggregate([ { "$match": { "_id": "abc" } }, { "$project": { "field": 1, "sales": { "$map": { "input": { "$filter": { "input": "$sales", "as": "saleItem", "cond": { "$eq": ["$$saleItem.label", 1] } } }, "as": "filteredSale", "in": { "label": "$$filteredSale.label", "codes": { "$filter": { "input": "$$filteredSale.codes", "as": "codeItem", "cond": { "$eq": ["$$codeItem.active", true] } } }, "prices": { "$filter": { "input": "$$filteredSale.prices", "as": "priceItem", "cond": { "$gt": ["$$priceItem.valid_from", new Date()] } } } } } } } } ])
逻辑说明
- 先用
$filter筛选出sales中label=1的元素; - 用
$map遍历每个筛选后的sales元素,对内部的codes和prices分别做$filter:codes只保留active=true的项;prices只保留valid_from大于当前日期的项。
若需保留sales中的其他字段,可在in对象内添加"$$filteredSale.目标字段名"。
二、数据结构优化建议
是否拆分sales到单独集合,需结合业务场景判断:
适合保留当前嵌入式结构的场景
- 查询时总是需要将
sales与主文档(field、misc等)一同返回; sales数组长度不会无限增长(比如每个主文档的sales项数固定或较少);- 对
sales的更新操作较少,或无需频繁单独修改某个sales项。
适合拆分到单独集合的场景
sales数组持续增长,可能导致主文档体积超过MongoDB单文档最大16MB限制;- 经常需要单独查询、更新某个
sales项,无需加载整个主文档; sales项数量远多于主文档数量,需为sales的label、valid_from等字段单独建索引以提升查询效率。
若拆分,推荐sales集合结构如下:
// Sales集合文档示例 { "_id": ObjectId("..."), "master_id": "abc", // 关联主文档的_id "label": 1, "codes": [...], "prices": [...] }
查询时可通过$lookup关联主文档,或根据业务需求分别查询两个集合。
内容的提问来源于stack exchange,提问作者Shark00n
相关产品推荐
相关产品推荐

