$unwind 展开数组
本教程共 50 篇 · 第 33 篇 · 更新于 2026-07-30 · 约 6 分钟阅读
33. $unwind 展开数组
本节目标:学会用 $unwind 把数组字段「炸开」成多行,让数组里的每个元素都成为一条独立文档,并知道何时用 preserveNullAndEmptyArrays 留住空数组文档。
很多统计要「按数组里的每个元素」来算,比如「每个商品卖了多少件」。可订单里的商品是 items 数组,直接 $group 没法逐行算。$unwind 就是把这个数组拆成多行的解法。
db.orders.insertMany([
{ _id: 1001, user_id: 1, items: [
{ product: "机械键盘", qty: 1, price: 399 },
{ product: "无线鼠标", qty: 2, price: 129 }
] },
{ _id: 1002, user_id: 2, items: [
{ product: "27寸显示器", qty: 1, price: 1099 }
] },
{ _id: 1003, user_id: 3, items: [] }
])
基本用法:数组变多行
$unwind: "$items" 会把每条订单,按 items 的元素数量「复制」成多条,每条里 items 不再是数组,而是单个对象:
db.orders.aggregate([
{ $unwind: "$items" }
])
_id:1001 拆成 2 条(键盘、鼠标各一条),_id:1002 拆成 1 条。原本的 user_id 等字段会原样保留在每条里。
Note拆完后
items字段成了普通对象,不是数组了。所以可以写"$items.product"、"$items.qty"直接访问子字段。
默认行为:空数组文档会被丢弃
注意上面的 _id:1003,它的 items 是空数组 []。默认情况下 $unwind 会把它整个丢掉,因为「没有元素可生成行」。
preserveNullAndEmptyArrays:保留空/缺失文档
加 preserveNullAndEmptyArrays: true,即使数组为空或字段缺失,原文档也保留下来(此时 items 为 null):
// 空数组的订单也保留,items 变为 null
db.orders.aggregate([
{ $unwind: { path: "$items", preserveNullAndEmptyArrays: true } }
])
这样 _id:1003 仍会出现在结果里,items 是 null。
Tip要不要保留空数组文档,看业务:统计「订单数」要保留;统计「商品销量」可丢弃(没有商品可算)。我的习惯是默认不带,需要保活时才加。
实战:拆行后按商品统计销量
配合 $group,就能算出每个商品总共卖了多少件:
db.orders.aggregate([
{ $unwind: "$items" },
{
$group: {
_id: "$items.product",
totalQty: { $sum: "$items.qty" }
}
}
])
结果形如 { _id: "机械键盘", totalQty: 1 }、{ _id: "无线鼠标", totalQty: 2 }。这正是 $unwind 最常见的搭档用法。
$unwind 与 $lookup 常联用
上一章讲过,订单关联商品表时,先 $unwind: "$items" 把商品行拆开,再 $lookup 关联 products,才能逐行匹配。两者是「数组类统计」的标准组合。
| 选项 | 效果 |
|---|---|
$unwind: "$字段" | 数组每个元素生成一行,空数组文档丢弃 |
preserveNullAndEmptyArrays: true | 空数组/缺失字段的文档也保留 |
下一章我们看聚合里那些「表达式」:日期、字符串、算术、条件判断,它们让计算更丰富。