退货率计算需确保分子分母同源且状态过滤准确:用$cond在$group中条件累加,嵌套退款需$anyelementtrue+$map判断,结果用$ifnull防除零、$round控精度。

退货率计算的核心逻辑是分母和分子必须来自同一数据切片
直接对全部订单用 $group 统计总数和退货数看似简单,但容易忽略状态过滤时机——如果退货订单本身还包含“已取消”“已退款失败”等子状态,$sum 时没做前置判断,结果就会失真。电商场景中,退货通常对应订单状态字段(如 status)为 "returned" 或子文档中 refund.status === "success",不能只看顶层字段。
用 $cond 在 $group 内区分退货与非退货订单
在 $group 阶段用 $cond 做条件累加,比先 $match 再分组更灵活,尤其当你要同时算多个指标(如总单数、退货数、拒收数)时,一次遍历即可。注意 $cond 的三个参数顺序:条件表达式、真值返回值、假值返回值。
示例聚合阶段:
[
{
"$group": {
"_id": null,
"total_orders": { "$sum": 1 },
"returned_orders": {
"$sum": {
"$cond": [
{ "$eq": ["$status", "returned"] },
1,
0
]
}
}
}
},
{
"$addFields": {
"return_rate": {
"$divide": ["$returned_orders", "$total_orders"]
}
}
}
]
常见错误:把 "$status" 写成 status(漏掉 $),或在 $cond 条件里用了 $eq 但没包数组(MongoDB 要求 $eq 参数必须是二元数组)。
退货状态可能嵌套在 refunds 数组里,需用 $anyElementTrue + $map
不少订单模型把退款信息存在数组字段 refunds 中,每个元素含 status 和 amount。此时不能直接 $eq 顶层字段,得先判断数组里是否存在成功退款记录。
实操建议:
- 用
$map提取所有refunds.status,生成布尔数组 - 用
$anyElementTrue判断是否有"success" - 再把这个布尔值传给外层
$cond作为条件
关键片段:
{
"$sum": {
"$cond": [
{
"$anyElementTrue": {
"$map": {
"input": "$refunds",
"as": "r",
"in": { "$eq": ["$$r.status", "success"] }
}
}
},
1,
0
]
}
}
聚合结果为小数,需用 $round 控制精度且避免除零
直接 $divide 可能返回 NaN(分母为 0)或超长小数(如 0.123456789)。电商报表通常要求保留两位小数,且空集合时应返回 0 而非 null。
安全写法:
- 用
$ifNull包裹分母,缺省为1避免除零 - 用
$round套两层:先算率,再四舍五入到 2 位 - 最终字段转成百分比字符串(可选):
{ "$multiply": [{ "$round": [...], 2 }, 100] }
退货率不是孤立数字——它依赖你如何定义“有效订单”和“有效退货”。比如预售订单未发货就取消,是否计入分母?部分退款是否算退货?这些业务规则必须先固化在聚合的 $match 或 $cond 条件里,而不是靠后期人工修正。











