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

PyMongo聚合投影如何将MongoDB的numberDecimal值转为数值或字符串

问题描述

我是MongoDB和PyMongo的新手,已在Mongo大学学习过相关课程。当前我需要处理嵌套文档,仅从中提取指定值,在尝试提取PriceEGC的数值部分时未成功,构造投影和提取指定值的代码如下:

import os
import math
import pymongo
from pprint import pprint
from datetime import datetime
from bson.json_util import dumps
from bson.decimal128 import Decimal128

# more code above not shown
for collection in all_collections[:1]:
    first_seen_date = collection.name.split("_")[-1]
    projection = {
        # (more projections)...
        "RegoExpiryDate": "$Vehicle.Registration.Expiry",
        "VIN": "$_id",
        "ComplianceDate": None,
        "PriceEGC": "$Price.FinalDisplayPrice",  # <- 问题出在这里
        "Price": None,
        "Reserve": "$Search.IsReservedDate",
        "StartingBid": None,
        "Odometer": "$Vehicle.Odometer",
        # (more projections)...
    }

    batch_size = 1
    num_batches = math.ceil(collection.count_documents({}) / batch_size)

    for num in range(1):  # range(num_batches):
        pipeline = [
            {"$match": {}},
            {"$project": projection},
            {"$skip": batch_size * num},
            {"$limit": batch_size},
        ]
        aggregation = list(collection.aggregate(pipeline))
        yield aggregation

if __name__ == "__main__":
    print(dumps(next(get_all_collections()), indent=2))

我会先打印单条聚合结果查看格式,再全量加载集合数据。
当前得到的非预期输出如下,PriceEGC字段为包含$numberDecimal键的对象结构:

[{
# (更多键值对)...
"RegoExpiryDate": "2021-08-31T00:00:00.000Z",
"VIN": "JTMRBREVX0D087618",
"ComplianceDate": null,
"PriceEGC": {
  "$numberDecimal": "36268.00"  # <- 不想要这个结构
},
"Price": null,
"Reserve": null,
"StartingBid": null,
"Odometer": 54567
# (更多键值对)...
}]

我期望的输出是PriceEGC直接对应数值36268.00或者字符串"36268.00":

[{
# (更多键值对)...
"RegoExpiryDate": "2021-08-31T00:00:00.000Z",
"VIN": "JTMRBREVX0D087618",
"ComplianceDate": null,
"PriceEGC": 36268.00,
/* 或 "PriceEGC": "36268.00", */
"Price": null,
"Reserve": null,
"StartingBid": null,
"Odometer": 54567
# (更多键值对)...
}]

我已经尝试过以下几种写法均未生效:

projection = {...,
"PriceEGC": "$Price.FinalDisplayPrice.$numberDecimal",
...
}

以及

projection = {...,
"PriceEGC": {"$toDecimal": "$Price.FinalDisplayPrice"}
...
}

还有

projection = {...,
"PriceEGC": Decimal128.to_decimal("$Price.FinalDisplayPrice")
...
}

同时也尝试修改聚合管道:

pipeline = [
    {"$match": {}},
    {"$project": projection},
    {"$toDecimal": "$Price.FinalDisplayPrice"},
    {"$skip": batch_size * num},
    {"$limit": batch_size},
]

请问该如何编写投影或聚合管道,才能得到符合预期的结果?


解决方案

根因说明

你看到的$numberDecimal嵌套结构不是MongoDB数据库内的实际存储结构,是bson.json_util.dumps工具序列化Decimal128类型字段时输出的MongoDB扩展JSON格式,不需要操作不存在的$numberDecimal子字段。

可行方案

方案1:聚合阶段直接转换类型(推荐)

在$project投影中直接用MongoDB原生聚合操作符,把Decimal128类型的字段提前转成字符串或数值,修改你的投影配置即可:

projection = {
    # 其余原有投影配置保持不变
    # 需要字符串格式选这个
    "PriceEGC": {"$toString": "$Price.FinalDisplayPrice"},
    # 需要数值格式选这个
    # "PriceEGC": {"$toDouble": "$Price.FinalDisplayPrice"},
    # 其余原有投影配置保持不变
}

转换完成后的字段序列化时就不会再出现$numberDecimal嵌套结构。

方案2:客户端侧手动转换

如果不想修改聚合管道,也可以在Python侧拿到聚合结果后,手动转换Decimal128对象的类型:

aggregation = list(collection.aggregate(pipeline))
# 遍历结果转换字段类型
for doc in aggregation:
    if doc.get("PriceEGC"):
        # 转成字符串
        doc["PriceEGC"] = str(doc["PriceEGC"])
        # 转成浮点数用这个写法
        # doc["PriceEGC"] = float(doc["PriceEGC"].to_decimal())
yield aggregation

之前方案失效的原因

  • 读取$Price.FinalDisplayPrice.$numberDecimal无效:该字段仅在JSON序列化时生成,数据库中不存在这个子字段
  • $toDecimal操作符是将其他类型转换为Decimal128类型,反而会保留你不想要的结构
  • 直接调用Python侧的Decimal128.to_decimal方法无效:聚合管道运行在MongoDB服务端,无法识别客户端的Python方法
  • 单独写$toDecimal阶段无效:聚合操作符必须包含在$project、$addFields这类阶段中,不能作为独立阶段使用

内容的提问来源于stack exchange,提问作者luminare

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 07:36:00