Docs 菜单
Docs 主页
/
数据库手册
/ / /

$unwind(聚合)

$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>
}
}
字段
类型
说明

字符串

数组字段的字段路径。如需指定字段路径,请在字段名称前加上美元符号 $,并用引号括起来。

字符串

可选。新字段的名称,用于保存该元素的数组索引。名称不能以美元符号 $ 开头。

布尔

可选。

  • 如果true ,如果path 为 null、缺失或空大量,$unwind 会输出文档。

  • false如果为path ,如果 为 null、缺失或空大量,则$unwind 不会输出文档。

默认值为 false

  • 如果操作数没有解析为数组,但没有缺失、为 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 }
])

如果满足以下条件,则 $unwindsizes 字段视为单元素数组:

  • 该字段存在,

  • 该值不为空,并且

  • 该值不是空数组。

$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 是一个填充数组。$unwindsizes 字段中的每个元素返回一个文档。

  • 在文档 "_id": 3 中,sizes 解析为单元素数组。

  • 文档 "_id": 2, "_id": 4"_id": 5 不会返回任何内容,因为 sizes 字段无法还原为单元素数组。

注意

{ path: <FIELD> } 语法是可选的。以下 $unwind 操作是等效的。

db.clothing.aggregate( [ { $unwind: "$sizes" } ] )
db.clothing.aggregate( [ { $unwind: { path: "$sizes" } } ] )

preserveNullAndEmptyArraysincludeArrayIndex 示例使用了以下集合:

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 }
])

以下 $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 }

以下 $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: Int32("5")
},
{
name: "envelopes",
tags: [ "stationary", "office" ],
price: NumberDecimal("19.95"),
quantity: Int32("8")
}
]
},
{
_id: "2",
items: [
{
name: "laptop",
tags: [ "office", "electronics" ],
price: NumberDecimal("800.00"),
quantity: Int32("1")
},
{
name: "notepad",
tags: [ "stationary", "school" ],
price: NumberDecimal("14.95"),
quantity: Int32("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", "ags: "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") }

本页上的C#示例使用Atlas示例数据集中的 sample_mflix数据库。要学习;了解如何创建免费的MongoDB Atlas 群集并加载示例数据集,请参阅MongoDB .NET/ C#驱动程序文档中的入门

以下 Movie 类对 sample_mflix.movies 集合中的文档进行建模:

public class Movie
{
public ObjectId Id { get; set; }
public int Runtime { get; set; }
public string Title { get; set; }
public string Rated { get; set; }
public List<string> Genres { get; set; }
public string Plot { get; set; }
public ImdbData Imdb { get; set; }
public int Year { get; set; }
public int Index { get; set; }
public string[] Comments { get; set; }
[BsonElement("lastupdated")]
public DateTime LastUpdated { get; set; }
}

注意

ConventionPack for Pascal Case

上述类中的属性以 Pascal 大小写命名,但MongoDB集合中的字段名称使用驼峰式大小写。为了解决这种差异,您可以使用以下代码在应用程序启动时注册 ConventionPack

var camelCaseConvention = new ConventionPack { new CamelCaseElementNameConvention() };
ConventionRegistry.Register("CamelCase", camelCaseConvention, type => true);

要使用MongoDB .NET/ C#驾驶员将 $unwind 阶段添加到聚合管道,请对 PipelineDefinition对象调用 Unwind() 方法。

以下示例创建了一个管道阶段,用于遍历每个输入 Movie文档中的 Genres字段。对于 Genres字段中的每个值,该阶段都会创建新的 Movie文档,并使用输入文档中的 Genres 值填充其 Genres字段。

var pipeline = new EmptyPipelineDefinition<Movie>()
.Unwind(m => m.Genres);

您可以使用 AggregateUnwindOptions 对象自定义 Unwind() 方法的行为。以下示例执行与上一示例相同的操作,但还包括以下选项:

  • PreserveNullAndEmptyArrays 确保输出中包含 Genres字段中包含空大量的文档。

  • IncludeArrayIndex 选项将名为 Index 的新字段添加到每个输出文档中。该字段的值是输入文档的 Genres大量中 Genres 字段值的大量索引。

var pipeline = new EmptyPipelineDefinition<Movie>()
.Unwind(m => m.Genres,
new AggregateUnwindOptions<Movie>()
{
PreserveNullAndEmptyArrays = true,
IncludeArrayIndex = new ExpressionFieldDefinition<Movie, int>(
m => m.Index)
});

后退

$unset

在此页面上