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
相关产品推荐
相关产品推荐

