对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。
Docs 菜单

$unwind(聚合阶段)

$unwind

解构输入文档中的数组字段,以便为每个元素输出文档。每个输出文档都是输入文档,并用该元素替换该数组字段的值。

可以使用 $unwind 查找托管在以下环境中的部署:

  • MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务

传递字段路径(Field Path)操作数或文档操作数以展开大量字段。

您可以将大量字段路径(Field Path)传递给 $unwind。使用此语法,如果字段值为 null、缺失或空大量,则 $unwind 不会输出文档。

{ $unwind: <field path> }

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

您可以将文档传递给 $unwind 来指定选项。

{
$unwind:
{
path: <field path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
字段
类型
说明

字符串

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

字符串

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

布尔

可选。

  • 如果为 true,如果 path 缺失或是空数组,$unwind 将在输出文档中忽略输出字段。如果值为 null,则该字段仍为 null。

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

默认值为 false。

当 path 的值未解析为数组时,$unwind 的行为如下:

  • 如果该值未缺失、不是 null 且不是空数组,则 $unwind 会按原样使用该值输出单个文档。

  • 如果指定了 includeArrayIndex,则数组输入的索引为 0,非数组输入的索引为 null。列表中靠后的文档的索引大于 0。

  • 如果该值缺失、为null 或空大量,则$unwind 遵循 keepNullAndEmptyArrays 选项。当指定includeArrayIndex 并保留文档时,索引为null 。

如果为输入文档中不存在的字段指定路径,或者该字段为空大量,则默认,$unwind 会忽略输入文档,并且不会输出该输入文档的文档。

要输出大量字段缺失、为 null 或空大量的文档,请使用preserveNullAndEmptyArrays 选项。

本页上的示例使用sample_mflix示例数据集中的数据。有关如何将此数据集加载到自管理MongoDB 部署中的详细信息,请参阅加载示例数据集。如果对示例数据库进行了任何修改,则可能需要删除并重新创建数据库才能运行本页上的示例。

以下聚合使用 $unwind 阶段为 Inception 电影文档的 genres大量中的每个元素输出一个文档:

db.movies.aggregate( [
{ $match: { title: "Inception" } },
{ $project: { _id: 0, title: 1, genres: 1 } },
{ $unwind: "$genres" }
] )
[
{
genres: 'Action',
title: 'Inception'
},
{
genres: 'Mystery',
title: 'Inception'
},
{
genres: 'Sci-Fi',
title: 'Inception'
}
]

每个输出文档都与输入文档相同,只是 genres字段的值不同,该字段现在包含原始 genres大量中的单个元素。

以下聚合使用具有四个电影文档的 $unwind 阶段。其中两个文档("La porta del cielo" 和 "Neecha Nagar")没有 genres字段:

db.movies.aggregate( [
{
$match: {
title: {
$in: [
"Inception",
"Brave",
"La porta del cielo",
"Neecha Nagar"
]
}
}
},
{ $project: { _id: 0, title: 1, genres: 1 } },
{ $unwind: { path: "$genres" } }
] )
[
{
genres: 'Animation',
title: 'Brave'
},
{
genres: 'Adventure',
title: 'Brave'
},
{
genres: 'Comedy',
title: 'Brave'
},
{
genres: 'Action',
title: 'Inception'
},
{
genres: 'Mystery',
title: 'Inception'
},
{
genres: 'Sci-Fi',
title: 'Inception'
}
]
  • 在 "Brave" 和 "Inception" 文档中,genres 是一个填充大量。 $unwind 会为每个元素返回一个文档。

  • "La porta del cielo" 和 "Neecha Nagar" 文档没有 genres字段,因此 $unwind 不会为它们返回任何文档。

注意

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

db.<COLLECTION>.aggregate(
[ { $unwind: "<FIELD>" } ]
)
db.<COLLECTION>.aggregate(
[ { $unwind: { path: "<FIELD>" } } ]
)

keepNullAndEmptyArrays 和 includeArrayIndex 示例使用 sample_mflix.movies集合中的文档。

以下 $unwind 操作使用preserveNullAndEmptyArrays 选项来包含缺少 genres字段的文档。

db.movies.aggregate( [
{
$match: {
title: {
$in: [
"Inception",
"Brave",
"La porta del cielo",
"Neecha Nagar"
]
}
}
},
{ $project: { _id: 0, title: 1, genres: 1 } },
{
$unwind: {
path: "$genres",
preserveNullAndEmptyArrays: true
}
}
] )
[
{
title: 'La porta del cielo'
},
{
title: 'Neecha Nagar'
},
{
genres: 'Animation',
title: 'Brave'
},
{
genres: 'Adventure',
title: 'Brave'
},
{
genres: 'Comedy',
title: 'Brave'
},
{
genres: 'Action',
title: 'Inception'
},
{
genres: 'Mystery',
title: 'Inception'
},
{
genres: 'Sci-Fi',
title: 'Inception'
}
]

以下 $unwind 操作使用 includeArrayIndex 选项在输出中包含大量索引。

db.movies.aggregate( [
{ $match: { title: "Inception" } },
{ $project: { _id: 0, title: 1, genres: 1 } },
{
$unwind: {
path: "$genres",
includeArrayIndex: "genreIndex"
}
}
] )
[
{
genres: 'Action',
title: 'Inception',
genreIndex: Long('0')
},
{
genres: 'Mystery',
title: 'Inception',
genreIndex: Long('1')
},
{
genres: 'Sci-Fi',
title: 'Inception',
genreIndex: Long('2')
}
]

以下管道展开 genres大量并按类型对生成的文档进行分组,以计算每种类型的电影数量:

db.movies.aggregate( [
// First Stage
{
$match: {
title: {
$in: [
"The Dark Knight",
"Inception",
"Interstellar",
"Brave"
]
}
}
},
// Second Stage
{ $project: { _id: 0, title: 1, genres: 1 } },
// Third Stage
{ $unwind: "$genres" },
// Fourth Stage
{
$group: {
_id: "$genres",
movieCount: { $sum: 1 }
}
},
// Fifth Stage
{ $sort: { movieCount: -1 } }
] )
[
{
_id: 'Adventure',
movieCount: 2
},
{
_id: 'Action',
movieCount: 2
},
{
_id: 'Drama',
movieCount: 2
},
{
_id: 'Sci-Fi',
movieCount: 2
},
{
_id: 'Crime',
movieCount: 1
},
{
_id: 'Animation',
movieCount: 1
},
{
_id: 'Comedy',
movieCount: 1
},
{
_id: 'Mystery',
movieCount: 1
}
]

您可以在单个管道中多次应用$unwind,以展开包含多个大量字段的文档。以下操作会展开 genres大量,然后展开 cast大量,为每个类型-演员组合生成一个平面文档,然后按类型分组以计算每个类型的演员阵容总数:

db.movies.aggregate( [
// First Stage
{
$match: {
title: {
$in: [ "Inception", "The Dark Knight", "Interstellar" ]
}
}
},
// Second Stage
{ $project: { _id: 0, title: 1, genres: 1, cast: 1 } },
// Third Stage
{ $unwind: "$genres" },
// Fourth Stage
{ $unwind: "$cast" },
// Fifth Stage
{
$group: {
_id: "$genres",
castAppearances: { $sum: 1 }
}
}
] )
[
{
_id: 'Adventure',
castAppearances: 4
},
{
_id: 'Crime',
castAppearances: 4
},
{
_id: 'Action',
castAppearances: 8
},
{
_id: 'Drama',
castAppearances: 8
},
{
_id: 'Mystery',
castAppearances: 4
},
{
_id: 'Sci-Fi',
castAppearances: 8
}
]

本页上的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; }
}

注意

用于 Pascal Case 的 ConventionPack

此页面上的 C# 类在其属性名称中使用 Pascal 命名法,而 MongoDB 集合中的字段名称则使用 camel 命名法。为了解决这种差异,可以在应用程序启动时使用以下代码注册一个 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)
});

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

要使用MongoDB Node.js驱动程序将 $unwind 阶段添加到聚合管道,请在管道对象中使用 $unwind操作符。

以下示例创建了一个管道阶段,用于遍历每个输入 movie文档中的 genres字段。对于 genres字段中的每个值,该阶段都会创建新的 movie文档,并使用输入文档中的 genres 值填充其 genres字段。然后,该示例运行聚合管道:

const pipeline = [{ $unwind: "$genres" }];
const cursor = collection.aggregate(pipeline);
return cursor;

您可以自定义 $unwind 方法的行为。以下示例执行与上一示例相同的操作,但还包括以下选项:

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

  • includeArrayIndex 向每个输出文档添加一个名为 index 的新字段。该字段包含输入文档的 genres字段中 genres 值的大量索引。

const pipeline = [
{
$unwind: {
path: "$genres",
preserveNullAndEmptyArrays: true,
includeArrayIndex: "index"
}
}
];
const cursor = collection.aggregate(pipeline);
return cursor;

有关相关阶段和表达式,请参阅 $group、$sum、$sort 和 $multiply。

有关完整示例,请参阅展开数组和数据分组教程。