MongoDB中“数据透视”本质是用$group+$push/$addToSet+$cond模拟行转列:先按维度(如"$subject")分组,再用$push收集数组,或用$cond分流条件值(如男女分数),配合$$REMOVE剔除无效项,实现宽表结构输出。

什么是 MongoDB 里的“数据透视”
MongoDB 本身没有 Pivot 关键字,所谓“数据透视”,本质是用 $group + $push / $addToSet + 条件表达式(如 $cond)模拟行转列效果。比如把“每个学生所属科目及分数”变成“每科下所有学生分数列表”,或更进一步:按性别统计各科平均分并横向展开。
用 $group + $push 收集数组字段
这是最基础的透视雏形,适用于“一维分组 + 多值聚合”场景:
-
_id指定分组维度(如"$subject"),不能为null否则全表归一组 -
$push把匹配字段值收进数组,例如{ scores: { $push: "$score" } } - 若需去重,改用
$addToSet;若只要第一个值,用$first - 注意:
$push不会自动排序,后续需$sort阶段或应用层处理
示例:按科目汇总所有学生成绩
db.students.aggregate([
{ $group: { _id: "$subject", scores: { $push: "$score" } } }
])
用 $cond + $push 实现条件列展开
真正接近 SQL PIVOT 的写法,靠 $cond 在 $group 内做字段分流:
- 每个目标列对应一个
$push字段,内部用{ $cond: { if: { $eq: ["$gender", "male"] }, then: "$score", else: "$$REMOVE" } } -
$$REMOVE是关键——它让不满足条件的文档不进入数组,避免填入null - 不能用
null或空字符串代替$$REMOVE,否则数组里会混入无效占位符 - 多个
$cond并列写在同一个$group阶段里,效率优于拆成多个管道
示例:按科目分组,分别收集男生、女生的分数数组
db.students.aggregate([
{
$group: {
_id: "$subject",
male_scores: {
$push: {
$cond: { if: { $eq: ["$gender", "male"] }, then: "$score", else: "$$REMOVE" }
}
},
female_scores: {
$push: {
$cond: { if: { $eq: ["$gender", "female"] }, then: "$score", else: "$$REMOVE" }
}
}
}
}
])
嵌套分组 + $arrayElemAt 提取特定位置值
当需要“每组取最高分/最低分/第 N 名”这类指标时,$push 后接 $sort 再用 $arrayElemAt 更可靠:
- 先
$push全部值,再$sort(必须在$group外,因为$group内不能直接排序数组) -
$arrayElemAt索引从 0 开始,-1表示末尾(即最大值,前提是已升序排好) - 如果原始数据量大,
$sort+$arrayElemAt比多次$max/$min更灵活(比如取 Top 3) - 注意:
$arrayElemAt对空数组返回null,业务逻辑需判空
示例:每科取分数最高的前三名学生姓名
db.students.aggregate([
{ $sort: { subject: 1, score: -1 } },
{
$group: {
_id: "$subject",
top3_names: { $push: "$name" }
}
},
{
$project: {
subject: "$_id",
top3_names: { $slice: ["$top3_names", 3] },
_id: 0
}
}
])
真正难的不是语法拼凑,而是想清楚“你要的透视结果到底是什么结构”——是宽表(固定列)、还是嵌套对象(动态键)、或是扁平化数组。MongoDB 不强制 schema,但聚合输出结构一旦定型,下游解析逻辑就得跟着适配。别为了“像 Excel 一样”硬套 $cond,该用应用层分组时就分组。

















