$unwind(聚合)
定义
兼容性
可以使用 $unwind
查找托管在以下环境中的部署:
MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务
MongoDB Enterprise:基于订阅、自我管理的 MongoDB 版本
MongoDB Community:源代码可用、免费使用且可自行管理的 MongoDB 版本
语法
您可传递字段路径操作数或文档操作数来展开数组字段。
字段路径操作数
您可以将数组字段路径传递给 $unwind
。使用该语法时,如果字段值为 null、缺失或空数组,则 $unwind
不会输出文档。
{ $unwind: <field path> }
如需指定字段路径,在字段名称前加上美元符号 $
,并用引号括起来。
带选项的文档操作数
您可以将文档传递给 $unwind
以指定各种行为选项。
{ $unwind: { path: <field path>, includeArrayIndex: <string>, preserveNullAndEmptyArrays: <boolean> } }
字段 | 类型 | 说明 |
---|---|---|
字符串 | 数组字段的字段路径。如需指定字段路径,请在字段名称前加上美元符号 | |
字符串 | 可选。新字段的名称,用于保存该元素的数组索引。名称不能以美元符号 | |
布尔 |
行为
非数组字段路径
如果操作数没有解析为数组,但没有缺失、为
null
或空数组,$unwind
会将该操作数视为单元素数组。当操作数为
null
、缺失或空数组时,$unwind
遵循为 preserveNullAndEmptyArrays 选项设置的行为。
缺失字段
如果为输入文档中不存在的字段指定路径,或者字段为空数组,$unwind
默认会忽略此输入文档,不会输出该输入文档的文档。
请使用 preserveNullAndEmptyArrays 选项输出数组字段缺失、为 null 或空数组的文档。
示例
展开数组
在 mongosh
中创建名为 inventory
的示例集合,其中包含以下文档:
db.inventory.insertOne({ "_id" : 1, "item" : "ABC1", sizes: [ "S", "M", "L"] })
以下聚合使用 $unwind
阶段为 sizes
数组中的每个元素输出一个文档:
db.inventory.aggregate( [ { $unwind : "$sizes" } ] )
操作返回以下结果:
{ "_id" : 1, "item" : "ABC1", "sizes" : "S" } { "_id" : 1, "item" : "ABC1", "sizes" : "M" } { "_id" : 1, "item" : "ABC1", "sizes" : "L" }
每个文档都与输入文档完全相同,只是 sizes
字段的值不同,该字段现在采用原始 sizes
数组的值。
缺少或非数组值
考虑 clothing
集合。
db.clothing.insertMany([ { "_id" : 1, "item" : "Shirt", "sizes": [ "S", "M", "L"] }, { "_id" : 2, "item" : "Shorts", "sizes" : [ ] }, { "_id" : 3, "item" : "Hat", "sizes": "M" }, { "_id" : 4, "item" : "Gloves" }, { "_id" : 5, "item" : "Scarf", "sizes" : null } ])
如果满足以下条件,则 $unwind
将 sizes
字段视为单元素数组:
该字段存在,
该值不为空,并且
该值不是空数组。
db.clothing.aggregate( [ { $unwind: { path: "$sizes" } } ] )
$unwind
操作返回:
{ _id: 1, item: 'Shirt', sizes: 'S' }, { _id: 1, item: 'Shirt', sizes: 'M' }, { _id: 1, item: 'Shirt', sizes: 'L' }, { _id: 3, item: 'Hat', sizes: 'M' }
在文档
"_id": 1
中,sizes
是一个填充数组。$unwind
为sizes
字段中的每个元素返回一个文档。在文档
"_id": 3
中,sizes
解析为单元素数组。文档
"_id": 2, "_id": 4
和"_id": 5
不会返回任何内容,因为sizes
字段无法还原为单元素数组。
注意
{ path: <FIELD> }
语法是可选的。以下 $unwind
操作是等效的。
db.clothing.aggregate( [ { $unwind: "$sizes" } ] ) db.clothing.aggregate( [ { $unwind: { path: "$sizes" } } ] )
preserveNullAndEmptyArrays
和 includeArrayIndex
preserveNullAndEmptyArrays
和 includeArrayIndex
示例使用了以下集合:
db.inventory2.insertMany([ { "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] }, { "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] }, { "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" }, { "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") }, { "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null } ])
preserveNullAndEmptyArrays
以下 $unwind
操作使用 preserveNullAndEmptyArrays 选项来纳入 sizes
字段为 null、缺失或空数组的文档。
db.inventory2.aggregate( [ { $unwind: { path: "$sizes", preserveNullAndEmptyArrays: true } } ] )
输出包括 sizes
字段为 null、缺失或空数组的文档:
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" } { "_id" : 2, "item" : "EFG", "price" : NumberDecimal("120") } { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" } { "_id" : 4, "item" : "LMN", "price" : NumberDecimal("10") } { "_id" : 5, "item" : "XYZ", "price" : NumberDecimal("5.75"), "sizes" : null }
includeArrayIndex
以下 $unwind
操作使用 includeArrayIndex 选项以在输出中纳入数组索引。
db.inventory2.aggregate( [ { $unwind: { path: "$sizes", includeArrayIndex: "arrayIndex" } }])
该操作展开 sizes
数组并将该数组索引包含在新的 arrayIndex
字段中。如果 sizes
字段未解析为填充数组,但不缺失、不为 null 或空数组,则 arrayIndex
字段为 null
。
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S", "arrayIndex" : NumberLong(0) } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M", "arrayIndex" : NumberLong(1) } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L", "arrayIndex" : NumberLong(2) } { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M", "arrayIndex" : null }
按未展开值分组
在 mongosh
中创建名为 inventory2
的示例集合,其中包含以下文档:
db.inventory2.insertMany([ { "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] }, { "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] }, { "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" }, { "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") }, { "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null } ])
以下管道会展开 sizes
数组,并按展开的大小值对生成的文档进行分组:
db.inventory2.aggregate( [ // First Stage { $unwind: { path: "$sizes", preserveNullAndEmptyArrays: true } }, // Second Stage { $group: { _id: "$sizes", averagePrice: { $avg: "$price" } } }, // Third Stage { $sort: { "averagePrice": -1 } } ] )
- 第一个阶段:
$unwind
阶段为sizes
数组中的每个元素输出一个新文档。该阶段使用 preserveNullAndEmptyArrays 选项以在输出中包含sizes
字段缺失、为 null 或空数组的文档。该阶段将以下文档传递到下一阶段:{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" } { "_id" : 2, "item" : "EFG", "price" : NumberDecimal("120") } { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" } { "_id" : 4, "item" : "LMN", "price" : NumberDecimal("10") } { "_id" : 5, "item" : "XYZ", "price" : NumberDecimal("5.75"), "sizes" : null } - 第二个阶段:
$group
阶段按sizes
对文档进行分组,并计算每种尺寸的平均价格。该阶段将以下文档传递到下一阶段:{ "_id" : "S", "averagePrice" : NumberDecimal("80") } { "_id" : "L", "averagePrice" : NumberDecimal("80") } { "_id" : "M", "averagePrice" : NumberDecimal("120") } { "_id" : null, "averagePrice" : NumberDecimal("45.25") } - 第三个阶段:
$sort
阶段按averagePrice
降序对文档进行排序。该操作返回以下结果:{ "_id" : "M", "averagePrice" : NumberDecimal("120") } { "_id" : "L", "averagePrice" : NumberDecimal("80") } { "_id" : "S", "averagePrice" : NumberDecimal("80") } { "_id" : null, "averagePrice" : NumberDecimal("45.25") }
展开嵌入式数组
在 mongosh
中创建名为 sales
的示例集合,其中包含以下文档:
db.sales.insertMany([ { _id: "1", "items" : [ { "name" : "pens", "tags" : [ "writing", "office", "school", "stationary" ], "price" : NumberDecimal("12.00"), "quantity" : NumberInt("5") }, { "name" : "envelopes", "tags" : [ "stationary", "office" ], "price" : NumberDecimal("19.95"), "quantity" : NumberInt("8") } ] }, { _id: "2", "items" : [ { "name" : "laptop", "tags" : [ "office", "electronics" ], "price" : NumberDecimal("800.00"), "quantity" : NumberInt("1") }, { "name" : "notepad", "tags" : [ "stationary", "school" ], "price" : NumberDecimal("14.95"), "quantity" : NumberInt("3") } ] } ])
下面的操作按标签对已售出的物品进行分组,然后计算每个标签的总销售额。
db.sales.aggregate([ // First Stage { $unwind: "$items" }, // Second Stage { $unwind: "$items.tags" }, // Third Stage { $group: { _id: "$items.tags", totalSalesAmount: { $sum: { $multiply: [ "$items.price", "$items.quantity" ] } } } } ])
- 第一个阶段:
第一个
$unwind
阶段为items
数组中的每个元素输出一个新文档:{ "_id" : "1", "items" : { "name" : "pens", "tags" : [ "writing", "office", "school", "stationary" ], "price" : NumberDecimal("12.00"), "quantity" : 5 } } { "_id" : "1", "items" : { "name" : "envelopes", "tags" : [ "stationary", "office" ], "price" : NumberDecimal("19.95"), "quantity" : 8 } } { "_id" : "2", "items" : { "name" : "laptop", "tags" : [ "office", "electronics" ], "price" : NumberDecimal("800.00"), "quantity" : 1 } } { "_id" : "2", "items" : { "name" : "notepad", "tags" : [ "stationary", "school" ], "price" : NumberDecimal("14.95"), "quantity" : 3 } } - 第二阶段
第二个
$unwind
阶段为items.tags
数组中的每个元素输出一个新文档:{ "_id" : "1", "items" : { "name" : "pens", "tags" : "writing", "price" : NumberDecimal("12.00"), "quantity" : 5 } } { "_id" : "1", "items" : { "name" : "pens", "tags" : "office", "price" : NumberDecimal("12.00"), "quantity" : 5 } } { "_id" : "1", "items" : { "name" : "pens", "tags" : "school", "price" : NumberDecimal("12.00"), "quantity" : 5 } } { "_id" : "1", "items" : { "name" : "pens", "tags" : "stationary", "price" : NumberDecimal("12.00"), "quantity" : 5 } } { "_id" : "1", "items" : { "name" : "envelopes", "tags" : "stationary", "price" : NumberDecimal("19.95"), "quantity" : 8 } } { "_id" : "1", "items" : { "name" : "envelopes", "tags" : "office", "price" : NumberDecimal("19.95"), "quantity" : 8 } } { "_id" : "2", "items" : { "name" : "laptop", "tags" : "office", "price" : NumberDecimal("800.00"), "quantity" : 1 } } { "_id" : "2", "items" : { "name" : "laptop", "tags" : "electronics", "price" : NumberDecimal("800.00"), "quantity" : 1 } } { "_id" : "2", "items" : { "name" : "notepad", "tags" : "stationary", "price" : NumberDecimal("14.95"), "quantity" : 3 } } { "_id" : "2", "items" : { "name" : "notepad", "tags" : "school", "price" : NumberDecimal("14.95"), "quantity" : 3 } } - 第三个阶段
$group
阶段按标签对文档进行分组,并计算每个标签的商品的总销售额:{ "_id" : "writing", "totalSalesAmount" : NumberDecimal("60.00") } { "_id" : "stationary", "totalSalesAmount" : NumberDecimal("264.45") } { "_id" : "electronics", "totalSalesAmount" : NumberDecimal("800.00") } { "_id" : "school", "totalSalesAmount" : NumberDecimal("104.85") } { "_id" : "office", "totalSalesAmount" : NumberDecimal("1019.60") }