$unwind (aggregation)

在本页面

Definition

  • $unwind
    • 从 Importing 文档解构数组字段,以输出* each *元素的文档。每个输出文档都是 Importing 文档,其中 array 字段的值被元素替换。

Syntax

您可以传递字段路径操作数或文档操作数来展开数组字段。

字段路径操作数

您可以将数组字段路径传递给$unwind。使用此语法时,如果字段值为空,丢失或空数组,则$unwind不会输出文档。

{ $unwind: <field path> }

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

带选项的文档操作数

3.2 版中的新功能。

您可以将文档传递到$unwind以指定各种行为选项。

{
  $unwind:
    {
      path: <field path>,
      includeArrayIndex: <string>,
      preserveNullAndEmptyArrays: <boolean>
    }
}
FieldTypeDescription
pathstring数组字段的字段路径。要指定字段路径,请在字段名称前加上美元符号$并用引号引起来。

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

如果true,如果path为空,缺少或为空数组,则$unwind输出文档。
如果false,如果path为空,缺少或为空数组,则$unwind不会输出文档。
缺省值为false

Behaviors

非数组字段路径

在版本 3.2 中进行了更改:$unwind不再在非数组操作数上出现错误。如果操作数不能解析为数组,但不丢失,为 null 或为空数组,则$unwind会将操作数视为单个元素数组。如果操作数为 null,丢失或为空数组,则$unwind的行为取决于preserveNullAndEmptyArrays选项的值。

以前,如果字段路径指定的字段中的值不是数组,则db.collection.aggregate()会产生错误。

Missing Field

如果为 Importing 文档中不存在的字段指定路径,或者该字段为空数组,则默认情况下$unwind会忽略 Importing 文档,并且不会输出该 Importing 文档的文档。

3.2 版中的新增功能:要输出缺少数组字段,空值或空数组的文档,请使用preserveNullAndEmptyArrays选项。

Examples

Unwind Array

mongoShell 程序中,使用以下文档创建一个名为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数组中的值之外,每个文档都与 Importing 文档相同。

includeArrayIndex 和 preserveNullAndEmptyArrays

3.2 版中的新功能。

mongoShell 程序中,使用以下文档创建一个名为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 }
])

以下$unwind操作是等效的,并为sizes字段中的每个元素返回一个文档。如果sizes字段不能解析为数组但不丢失,为 null 或为空数组,则$unwind会将非数组操作数视为单个元素数组。

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

该操作返回以下文档:

{ "_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" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }

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 }

preserveNullAndEmptyArrays

以下$unwind操作使用preserveNullAndEmptyArrays选项包括sizes字段为空,丢失或空数组的文档。

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

输出包括sizes字段为空,丢失或为空数组的那些文档:

{ "_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 }

按展开值分组

mongoShell 程序中,使用以下文档创建一个名为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 }
   }
] )
  • First Stage:

    • $unwind阶段为sizes数组中的每个元素输出一个新文档。该阶段使用preserveNullAndEmptyArrays选项在输出中包括缺少sizes字段,空值或空数组的那些文档。此阶段将以下文档传递到下一阶段:
{ "_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:

    • $group阶段按sizes对文档进行分组,然后计算每种尺寸的平均价格。此阶段将以下文档传递到下一阶段:
{ "_id" : "S", "averagePrice" : NumberDecimal("80") }
{ "_id" : "L", "averagePrice" : NumberDecimal("80") }
{ "_id" : "M", "averagePrice" : NumberDecimal("120") }
{ "_id" : null, "averagePrice" : NumberDecimal("45.25") }
  • Third Stage:

    • $sort阶段按averagePrice降序对文档进行排序。该操作返回以下结果:
{ "_id" : "M", "averagePrice" : NumberDecimal("120") }
{ "_id" : "L", "averagePrice" : NumberDecimal("80") }
{ "_id" : "S", "averagePrice" : NumberDecimal("80") }
{ "_id" : null, "averagePrice" : NumberDecimal("45.25") }

See also

展开嵌入式阵列

mongoShell 程序中,使用以下文档创建一个名为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("1.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" ] }
          }
      }
  }
])
  • First Stage

    • 第一个$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 } }
  • Second Stage

    • 第二个$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 } }
  • Third Stage

    • $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") }

Additional Resources