You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoEngine查询返回所有匹配条件嵌入文档的实现方法

嵌套嵌入文档全量匹配过滤实现方案

场景说明

基于Django + MongoEngine操作MongoDB数据时,投影阶段使用$elemMatch过滤数组类型的嵌入文档字段,仅能返回数组中第一个匹配条件的元素,无法满足返回所有匹配嵌入文档的业务需求。

模型定义

嵌入文档 Data

class Data(EmbeddedDocument):
    v = FloatField()
    q = StringField()
    co2 = FloatField()
    price = FloatField()
    ts = DateTimeField()

主文档 MetersData

class MetersData(DynamicDocument):
    meta = {'collection': 'dk_heating'}
    _id = ObjectIdField()
    ident = StringField()
    meteringPointId = StringField()
    customer = StringField()
    cvr = StringField()
    type = StringField()
    unit = StringField()
    address = StringField()
    period = EmbeddedDocumentField(Period)
    hourly_data = ListField(EmbeddedDocumentField(Data), db_field='data')
    daily_data = ListField(EmbeddedDocumentField(Data))
    monthly_data = ListField(EmbeddedDocumentField(Data))
    # monthly_data = EmbeddedDocumentListField(Data)
    yearly_data = ListField(EmbeddedDocumentField(Data))

原有问题查询

原查询写法如下,该写法受$elemMatch投影的设计限制,仅能返回monthly_data数组中第一个q="E"的元素:

MetersData.objects.filter(address=address, customer=customer).fields(
                monthly_data={"$elemMatch": {"q": "E"}},
                address=1, customer=1, cvr=1, ident=1, meteringPointId=1, type=1, unit=1, period=1)

原查询返回结果示例

可行实现方案

方案1:聚合管道$filter实现(推荐,MongoDB 3.2+版本支持)

使用聚合管道的$filter操作符可以在数据库层面完成数组过滤,直接返回所有符合条件的数组元素,性能开销最低,MongoEngine可直接调用聚合接口实现:

pipeline = [
    # 匹配主文档层面的过滤条件,与原查询filter逻辑一致
    {"$match": {"address": address, "customer": customer}},
    # 指定返回字段,同时对目标数组字段做全量条件过滤
    {"$project": {
        "address": 1,
        "customer": 1,
        "cvr": 1,
        "ident": 1,
        "meteringPointId": 1,
        "type": 1,
        "unit": 1,
        "period": 1,
        # 过滤monthly_data数组,保留所有q="E"的元素
        "monthly_data": {
            "$filter": {
                "input": "$monthly_data",
                "as": "item",
                "cond": {"$eq": ["$$item.q", "E"]}
            }
        }
    }}
]
# 执行聚合查询获取结果
result = MetersData.objects.aggregate(pipeline)

如果需要同时过滤hourly_data/daily_data等其他数组字段,在$project块中按相同语法添加对应字段的过滤规则即可。

方案2:字段类型替换+代码层面二次过滤

如果不想使用聚合管道,可以将monthly_data的字段类型从普通ListField(EmbeddedDocumentField(Data))替换为代码中注释的EmbeddedDocumentListField(Data),该类型是MongoEngine专为嵌入文档数组设计的字段类型,支持数组元素的条件匹配:

# 第一步:修改字段定义
monthly_data = EmbeddedDocumentListField(Data)

# 第二步:查询匹配包含目标元素的主文档
queryset = MetersData.objects.filter(
    address=address, 
    customer=customer,
    monthly_data__q="E"
).only("address", "customer", "cvr", "ident", "meteringPointId", "type", "unit", "period", "monthly_data")

# 第三步:代码层面二次过滤每个文档的monthly_data数组,保留符合条件的元素
for doc in queryset:
    doc.monthly_data = [item for item in doc.monthly_data if item.q == "E"]
    # 后续直接使用处理后的doc即可

注意:该方案会把匹配主文档的全量monthly_data数组加载到内存后再过滤,数组长度较大时网络传输和内存开销明显高于方案1,非特殊场景优先选择方案1。

内容的提问来源于stack exchange,提问作者Muhammad Haris Yousaf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 12:09:13