$unwind (aggregation)

Definition

  • $unwind
  • Deconstructs an array field from the input documents to output adocument for each element. Each output document is the inputdocument with the value of the array field replaced by the element.

Syntax

You can pass a field path operand or a document operand to unwind anarray field.

Field Path Operand

You can pass the array field path to $unwind. When usingthis syntax, $unwind does not output a document if the fieldvalue is null, missing, or an empty array.

  1. { $unwind: <field path> }

When you specify the field path, prefix thefield name with a dollar sign $ and enclose in quotes.

Document Operand with Options

New in version 3.2.

You can pass a document to $unwind to specify variousbehavior options.

  1. {
  2. $unwind:
  3. {
  4. path: <field path>,
  5. includeArrayIndex: <string>,
  6. preserveNullAndEmptyArrays: <boolean>
  7. }
  8. }
FieldTypeDescription
pathstringField path to an array field. To specify a field path, prefixthe field name with a dollar sign $ and enclose in quotes.
includeArrayIndexstringOptional. The name of a new field to hold the array index of theelement. The name cannot start with a dollar sign $.
preserveNullAndEmptyArraysbooleanOptional.- If true, if the path is null, missing, or an emptyarray, $unwind outputs the document.- If false, if path is null, missing, or an emptyarray, $unwind does not output a document.The default value is false.

Behaviors

Non-Array Field Path

Changed in version 3.2: $unwind stage no longer errors on non-array operands. Ifthe operand does not resolve to an array but is not missing, null,or an empty array, $unwind treats the operand as asingle element array. If the operand is null, missing, or an emptyarray, the behavior of $unwind depends on the value ofthe preserveNullAndEmptyArraysoption.

Previously, if a value in the field specified by the field path isnot an array, db.collection.aggregate() generates anerror.

Missing Field

If you specify a path for a field that does not exist in an inputdocument or the field is an empty array, $unwind, bydefault, ignores the input document and will not output documents forthat input document.

New in version 3.2: To output documents where the array field is missing, null or anempty array, use thepreserveNullAndEmptyArraysoption.

Examples

Unwind Array

From the mongo shell, create a sample collection namedinventory with the following document:

  1. db.inventory.insertOne({ "_id" : 1, "item" : "ABC1", sizes: [ "S", "M", "L"] })

The following aggregation uses the $unwind stage to outputa document for each element in the sizes array:

  1. db.inventory.aggregate( [ { $unwind : "$sizes" } ] )

The operation returns the following results:

  1. { "_id" : 1, "item" : "ABC1", "sizes" : "S" }
  2. { "_id" : 1, "item" : "ABC1", "sizes" : "M" }
  3. { "_id" : 1, "item" : "ABC1", "sizes" : "L" }

Each document is identical to the input document except for the valueof the sizes field which now holds a value from the originalsizes array.

includeArrayIndex and preserveNullAndEmptyArrays

New in version 3.2.

From the mongo shell, create a sample collection namedinventory2 with the following documents:

  1. db.inventory2.insertMany([
  2. { "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] },
  3. { "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] },
  4. { "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" },
  5. { "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") },
  6. { "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null }
  7. ])

The following $unwind operations are equivalent and returna document for each element in the sizes field. If the sizesfield does not resolve to an array but is not missing, null, or anempty array, $unwind treats the non-array operand as asingle element array.

  1. db.inventory2.aggregate( [ { $unwind: "$sizes" } ] )
  2. db.inventory2.aggregate( [ { $unwind: { path: "$sizes" } } ] )

The operation returns the following documents:

  1. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
  2. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
  3. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
  4. { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }

includeArrayIndex

The following $unwind operation uses theincludeArrayIndex option to includethe array index in the output.

  1. db.inventory2.aggregate( [
  2. {
  3. $unwind:
  4. {
  5. path: "$sizes",
  6. includeArrayIndex: "arrayIndex"
  7. }
  8. }])

The operation unwinds the sizes array and includes the array indexof the array index in the new arrayIndex field. If the sizesfield does not resolve to an array but is not missing, null, or anempty array, the arrayIndex field is null.

  1. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S", "arrayIndex" : NumberLong(0) }
  2. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M", "arrayIndex" : NumberLong(1) }
  3. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L", "arrayIndex" : NumberLong(2) }
  4. { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M", "arrayIndex" : null }

preserveNullAndEmptyArrays

The following $unwind operation uses thepreserveNullAndEmptyArraysoption to include documents whose sizes field is null, missing,or an empty array.

  1. db.inventory2.aggregate( [
  2. { $unwind: { path: "$sizes", preserveNullAndEmptyArrays: true } }
  3. ] )

The output includes those documents where the sizes field isnull, missing, or an empty array:

  1. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
  2. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
  3. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
  4. { "_id" : 2, "item" : "EFG", "price" : NumberDecimal("120") }
  5. { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
  6. { "_id" : 4, "item" : "LMN", "price" : NumberDecimal("10") }
  7. { "_id" : 5, "item" : "XYZ", "price" : NumberDecimal("5.75"), "sizes" : null }

Group by Unwound Values

From the mongo shell, create a sample collection namedinventory2 with the following documents:

  1. db.inventory2.insertMany([
  2. { "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] },
  3. { "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] },
  4. { "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" },
  5. { "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") },
  6. { "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null }
  7. ])

The following pipeline unwinds the sizes array and groups theresulting documents by the unwound size values:

  1. db.inventory2.aggregate( [
  2. // First Stage
  3. {
  4. $unwind: { path: "$sizes", preserveNullAndEmptyArrays: true }
  5. },
  6. // Second Stage
  7. {
  8. $group:
  9. {
  10. _id: "$sizes",
  11. averagePrice: { $avg: "$price" }
  12. }
  13. },
  14. // Third Stage
  15. {
  16. $sort: { "averagePrice": -1 }
  17. }
  18. ] )
  • First Stage:
  • The $unwind stage outputs a new document for each elementin the sizes array. The stage uses thepreserveNullAndEmptyArraysoption to include in the output those documents where sizes fieldis missing, null or an empty array. This stage passes the followingdocuments to the next stage:
  1. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
  2. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
  3. { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
  4. { "_id" : 2, "item" : "EFG", "price" : NumberDecimal("120") }
  5. { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
  6. { "_id" : 4, "item" : "LMN", "price" : NumberDecimal("10") }
  7. { "_id" : 5, "item" : "XYZ", "price" : NumberDecimal("5.75"), "sizes" : null }
  • Second Stage:
  • The $group stage groups the documents by sizesand calculates the average price of each size. This stagepasses the following documents to the next stage:
  1. { "_id" : "S", "averagePrice" : NumberDecimal("80") }
  2. { "_id" : "L", "averagePrice" : NumberDecimal("80") }
  3. { "_id" : "M", "averagePrice" : NumberDecimal("120") }
  4. { "_id" : null, "averagePrice" : NumberDecimal("45.25") }
  • Third Stage:
  • The $sort stage sorts the documents by averagePrice indescending order. The operation returns the following result:
  1. { "_id" : "M", "averagePrice" : NumberDecimal("120") }
  2. { "_id" : "L", "averagePrice" : NumberDecimal("80") }
  3. { "_id" : "S", "averagePrice" : NumberDecimal("80") }
  4. { "_id" : null, "averagePrice" : NumberDecimal("45.25") }

See also

Unwind Embedded Arrays

From the mongo shell, create a sample collection namedsales with the following documents:

  1. db.sales.insertMany([
  2. {
  3. _id: "1",
  4. "items" : [
  5. {
  6. "name" : "pens",
  7. "tags" : [ "writing", "office", "school", "stationary" ],
  8. "price" : NumberDecimal("12.00"),
  9. "quantity" : NumberInt("5")
  10. },
  11. {
  12. "name" : "envelopes",
  13. "tags" : [ "stationary", "office" ],
  14. "price" : NumberDecimal("1.95"),
  15. "quantity" : NumberInt("8")
  16. }
  17. ]
  18. },
  19. {
  20. _id: "2",
  21. "items" : [
  22. {
  23. "name" : "laptop",
  24. "tags" : [ "office", "electronics" ],
  25. "price" : NumberDecimal("800.00"),
  26. "quantity" : NumberInt("1")
  27. },
  28. {
  29. "name" : "notepad",
  30. "tags" : [ "stationary", "school" ],
  31. "price" : NumberDecimal("14.95"),
  32. "quantity" : NumberInt("3")
  33. }
  34. ]
  35. }
  36. ])

The following operation groups the items sold by their tags andcalculates the total sales amount per each tag.

  1. db.sales.aggregate([
  2. // First Stage
  3. { $unwind: "$items" },
  4.  
  5. // Second Stage
  6. { $unwind: "$items.tags" },
  7.  
  8. // Third Stage
  9. {
  10. $group:
  11. {
  12. _id: "$items.tags",
  13. totalSalesAmount:
  14. {
  15. $sum: { $multiply: [ "$items.price", "$items.quantity" ] }
  16. }
  17. }
  18. }
  19. ])
  • First Stage
  • The first $unwind stage outputs a new document foreach element in the items array:
  1. { "_id" : "1", "items" : { "name" : "pens", "tags" : [ "writing", "office", "school", "stationary" ], "price" : NumberDecimal("12.00"), "quantity" : 5 } }
  2. { "_id" : "1", "items" : { "name" : "envelopes", "tags" : [ "stationary", "office" ], "price" : NumberDecimal("19.95"), "quantity" : 8 } }
  3. { "_id" : "2", "items" : { "name" : "laptop", "tags" : [ "office", "electronics" ], "price" : NumberDecimal("800.00"), "quantity" : 1 } }
  4. { "_id" : "2", "items" : { "name" : "notepad", "tags" : [ "stationary", "school" ], "price" : NumberDecimal("14.95"), "quantity" : 3 } }
  • Second Stage
  • The second $unwind stage outputs a new document for eachelement in the items.tags arrays:
  1. { "_id" : "1", "items" : { "name" : "pens", "tags" : "writing", "price" : NumberDecimal("12.00"), "quantity" : 5 } }
  2. { "_id" : "1", "items" : { "name" : "pens", "tags" : "office", "price" : NumberDecimal("12.00"), "quantity" : 5 } }
  3. { "_id" : "1", "items" : { "name" : "pens", "tags" : "school", "price" : NumberDecimal("12.00"), "quantity" : 5 } }
  4. { "_id" : "1", "items" : { "name" : "pens", "tags" : "stationary", "price" : NumberDecimal("12.00"), "quantity" : 5 } }
  5. { "_id" : "1", "items" : { "name" : "envelopes", "tags" : "stationary", "price" : NumberDecimal("19.95"), "quantity" : 8 } }
  6. { "_id" : "1", "items" : { "name" : "envelopes", "tags" : "office", "price" : NumberDecimal("19.95"), "quantity" : 8 } }
  7. { "_id" : "2", "items" : { "name" : "laptop", "tags" : "office", "price" : NumberDecimal("800.00"), "quantity" : 1 } }
  8. { "_id" : "2", "items" : { "name" : "laptop", "tags" : "electronics", "price" : NumberDecimal("800.00"), "quantity" : 1 } }
  9. { "_id" : "2", "items" : { "name" : "notepad", "tags" : "stationary", "price" : NumberDecimal("14.95"), "quantity" : 3 } }
  10. { "_id" : "2", "items" : { "name" : "notepad", "tags" : "school", "price" : NumberDecimal("14.95"), "quantity" : 3 } }
  • Third Stage
  • The $group stage groups the documents by the tag andcalculates the total sales amount of items with each tag:
  1. { "_id" : "writing", "totalSalesAmount" : NumberDecimal("60.00") }
  2. { "_id" : "stationary", "totalSalesAmount" : NumberDecimal("264.45") }
  3. { "_id" : "electronics", "totalSalesAmount" : NumberDecimal("800.00") }
  4. { "_id" : "school", "totalSalesAmount" : NumberDecimal("104.85") }
  5. { "_id" : "office", "totalSalesAmount" : NumberDecimal("1019.60") }

See also

Additional Resources