$unwind (aggregation)
Definition
Compatibility
You can use $unwind for deployments hosted in the following
environments:
MongoDB Atlas: The fully managed service for MongoDB deployments in the cloud
MongoDB Enterprise: The subscription-based, self-managed version of MongoDB
MongoDB Community: The source-available, free-to-use, and self-managed version of MongoDB
Syntax
You can pass a field path operand or a document operand to unwind an array field.
Field Path Operand
You can pass the array field path to $unwind. When using
this syntax, $unwind does not output a document if the field
value is null, missing, or an empty array.
{ $unwind: <field path> }
When you specify the field path, prefix the
field name with a dollar sign $ and enclose in quotes.
Document Operand with Options
You can pass a document to $unwind to specify various
behavior options.
{ $unwind: { path: <field path>, includeArrayIndex: <string>, preserveNullAndEmptyArrays: <boolean> } }
Field | Type | Description |
|---|---|---|
string | Field path to an array field. To specify a field path, prefix
the field name with a dollar sign | |
string | Optional. The name of a new field to hold the array index of the
element. The name cannot start with a dollar sign | |
boolean |
Behaviors
Non-Array Field Path
When the operand does not resolve to an array, but is not missing,
null, or an empty array,$unwindtreats the operand as a single element array.When the operand is
null, missing, or an empty array$unwindfollows the behavior set for the preserveNullAndEmptyArrays option.
Missing Field
If you specify a path for a field that does not exist in an input
document or the field is an empty array, $unwind, by
default, ignores the input document and will not output documents for
that input document.
To output documents where the array field is missing, null or an empty array, use the preserveNullAndEmptyArrays option.
Examples
Unwind Array
In mongosh, create a sample collection named
inventory with the following document:
db.inventory.insertOne({ _id: 1, item: "ABC1", sizes: [ "S", "M", "L"] })
The following aggregation uses the $unwind stage to output
a document for each element in the sizes array:
db.inventory.aggregate( [ { $unwind : "$sizes" } ] )
The operation returns the following results:
{ _id: 1, item: "ABC1", sizes: "S" } { _id: 1, item: "ABC1", sizes: "M" } { _id: 1, item: "ABC1", sizes: "L" }
Each document is identical to the input document except for the value
of the sizes field which now holds a value from the original
sizes array.
Missing or Non-array Values
Consider the clothing collection:
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 treats the sizes field as a single element
array if:
the field is present,
the value is not null, and
the value is not an empty array.
Expand the sizes arrays with $unwind:
db.clothing.aggregate( [ { $unwind: { path: "$sizes" } } ] )
The $unwind operation returns:
{ _id: 1, item: 'Shirt', sizes: 'S' }, { _id: 1, item: 'Shirt', sizes: 'M' }, { _id: 1, item: 'Shirt', sizes: 'L' }, { _id: 3, item: 'Hat', sizes: 'M' }
In document
"_id": 1,sizesis a populated array.$unwindreturns a document for each element in thesizesfield.In document
"_id": 3,sizesresolves to a single element array.Documents
"_id": 2, "_id": 4, and"_id": 5do not return anything because thesizesfield cannot be reduced to a single element array.
Note
The { path: <FIELD> } syntax is optional. The following
$unwind operations are equivalent.
db.clothing.aggregate( [ { $unwind: "$sizes" } ] ) db.clothing.aggregate( [ { $unwind: { path: "$sizes" } } ] )
preserveNullAndEmptyArrays and includeArrayIndex
The preserveNullAndEmptyArrays and includeArrayIndex examples
use the following collection:
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
The following $unwind operation uses the
preserveNullAndEmptyArrays
option to include documents whose sizes field is null, missing,
or an empty array.
db.inventory2.aggregate( [ { $unwind: { path: "$sizes", preserveNullAndEmptyArrays: true } } ] )
The output includes those documents where the sizes field is
null, missing, or an empty array:
{ _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
The following $unwind operation uses the
includeArrayIndex option to include
the array index in the output.
db.inventory2.aggregate( [ { $unwind: { path: "$sizes", includeArrayIndex: "arrayIndex" } }])
The operation unwinds the sizes array and includes the array index
in the new arrayIndex field. If the sizes field does not
resolve to a populated array but is not missing, null, or an empty
array, the arrayIndex field is 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 }
Group by Unwound Values
In mongosh, create a sample collection named
inventory2 with the following documents:
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 } ])
The following pipeline unwinds the sizes array and groups the
resulting documents by the unwound size values:
db.inventory2.aggregate( [ // First Stage { $unwind: { path: "$sizes", preserveNullAndEmptyArrays: true } }, // Second Stage { $group: { _id: "$sizes", averagePrice: { $avg: "$price" } } }, // Third Stage { $sort: { "averagePrice": -1 } } ] )
- First Stage:
The
$unwindstage outputs a new document for each element in thesizesarray. The stage uses the preserveNullAndEmptyArrays option to include in the output those documents wheresizesfield is missing, null or an empty array. This stage passes the following documents to the next stage:{ _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 } - Second Stage:
The
$groupstage groups the documents bysizesand calculates the average price of each size. This stage passes the following documents to the next stage:{ _id: "S", averagePrice: NumberDecimal("80") } { _id: "L", averagePrice: NumberDecimal("80") } { _id: "M", averagePrice: NumberDecimal("120") } { _id: null, averagePrice: NumberDecimal("45.25") } - Third Stage:
The
$sortstage sorts the documents byaveragePricein descending order. The operation returns the following result:{ _id" : "M", averagePrice: NumberDecimal("120") } { _id" : "L", averagePrice: NumberDecimal("80") } { _id" : "S", averagePrice: NumberDecimal("80") } { _id" : null, averagePrice: NumberDecimal("45.25") }
See also:
Unwind Embedded Arrays
In mongosh, create a sample collection named
sales with the following documents:
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") } ] } ])
The following operation groups the items sold by their tags and calculates the total sales amount per each tag.
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" ] } } } } ])
- First Stage
The first
$unwindstage outputs a new document for each element in theitemsarray:{ _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 } } - Second Stage
The second
$unwindstage outputs a new document for each element in theitems.tagsarrays:{ _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 } } - Third Stage
The
$groupstage groups the documents by the tag and calculates the total sales amount of items with each tag:{ _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") }
The C# examples on this page use the sample_mflix database
from the Atlas sample datasets. To learn how to create a
free MongoDB Atlas cluster and load the sample datasets, see
Get Started in the MongoDB .NET/C#
Driver documentation.
The following Movie class models the documents in the sample_mflix.movies
collection:
public class Movie { public string 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; } [] public DateTime LastUpdated { get; set; } }
To use the MongoDB .NET/C# driver to add a $unwind stage to an aggregation
pipeline, call the Unwind() method on a PipelineDefinition object.
The following example creates a pipeline stage that iterates over the Genres field in each input Movie document. For each
value in the Genres field, the stage creates a new Movie document and
populates its Genres field with the Genres value from the input document.
var pipeline = new EmptyPipelineDefinition<Movie>() .Unwind(m => m.Genres);
You can use an AggregateUnwindOptions
object to customize the behavior of the Unwind() method.
The following example performs the same operation as the previous
example, but also includes the following options:
PreserveNullAndEmptyArraysensures that documents that contain an empty array in theGenresfield are included in the output.The
IncludeArrayIndexoption adds a new field namedIndexto each output document. The value of this field is the array index of theGenresfield's value in the input document'sGenresarray.
var pipeline = new EmptyPipelineDefinition<Movie>() .Unwind(m => m.Genres, new AggregateUnwindOptions<Movie>() { PreserveNullAndEmptyArrays = true, IncludeArrayIndex = new ExpressionFieldDefinition<Movie, int>( m => m.Index) });
See also: