MongoDB中的$ unwind运算符是什么?


103

这是我在MongoDB的第一天,所以请和我一起轻松:)

我听不懂$unwind操作员,也许是因为英语不是我的母语。

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

我想我可以理解项目操作员(就像SELECT,不是吗?)。但是,然后$unwind(citing)为每个源文档中未展开数组的每个成员返回一个文档

这像JOIN吗?如果是,如何的结果$project(有_idauthortitletags字段)可以用比较tags阵列?

注意:我从MongoDB网站获取了示例,我不知道tags数组的结构。我认为这是一个简单的标签名称数组。

Answers:


235

首先,欢迎来到MongoDB!

要记住的事情是,MongoDB使用“ NoSQL”方法进行数据存储,因此从您的思维中消失了选择,联接等思想。它以文件和集合的形式存储数据的方式,这允许动态地从存储位置添加和获取数据。

话虽如此,为了理解$ unwind参数背后的概念,您首先必须了解试图引用的用例在说什么。mongodb.org的示例文档如下:

{
 title : "this is my title" ,
 author : "bob" ,
 posted : new Date () ,
 pageViews : 5 ,
 tags : [ "fun" , "good" , "fun" ] ,
 comments : [
             { author :"joe" , text : "this is cool" } ,
             { author :"sam" , text : "this is bad" }
 ],
 other : { foo : 5 }
}

请注意,标签实际上是由3个元素组成的数组,在这种情况下为“ fun”,“ good”和“ fun”。

$ unwind的作用是让您为每个元素剥离一个文档并返回生成的文档。以经典的方式来考虑这一点,就相当于“对于标签数组中的每个项目,仅返回具有该项目的文档”。

因此,运行以下命令的结果:

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

将返回以下文件:

{
     "result" : [
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "fun"
             },
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "good"
             },
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "fun"
             }
     ],
     "OK" : 1
}

注意,结果数组中唯一发生变化的是在标签值中返回的内容。如果您需要有关其工作原理的其他参考,我在此处提供了一个链接。希望这会有所帮助,并祝您进入我迄今为止遇到的最好的NoSQL系统之一好运。


44

$unwind 每个管道元素重复一次管道中的每个文档。

因此,如果您的输入管道包含一个文章文档,其中包含两个元素tags{$unwind: '$tags'}则会将该管道转换为两个相同的文章文档(tags字段除外)。在第一个文档中,tags将包含原始文档数组中的第一个元素,在第二个文档中,tags将包含第二个元素。


22

让我们通过一个例子来了解它

这是怎样的公司文档的样子:

原始文件

$unwind允许我们采取文档作为输入的是有一个数组值字段并产生输出文档,使得有一个为阵列中的每个元件一个输出文档。资源

$ unwind阶段

因此,让我们回到我们公司的示例中,看看放松阶段的使用。该查询:


db.companies.aggregate([
    { $match: {"funding_rounds.investments.financial_org.permalink": "greylock" } },
    { $project: {
        _id: 0,
        name: 1,
        amount: "$funding_rounds.raised_amount",
        year: "$funding_rounds.funded_year"
    } }
])

生成具有数量和年份数组的文档。

项目成果

因为我们正在访问融资轮次数组中每个元素的筹集金额和融资年。要解决此问题,我们可以在聚合阶段的项目阶段之前包括一个展开阶段,并通过说我们希望进行unwind融资回合数组来对其进行参数化:


db.companies.aggregate([
    { $match: {"funding_rounds.investments.financial_org.permalink": "greylock" } },
    { $unwind: "$funding_rounds" },
    { $project: {
        _id: 0,
        name: 1,
        amount: "$funding_rounds.raised_amount",
        year: "$funding_rounds.funded_year"
    } }
])

展开的效果是,输出到下一阶段的文档数量多于输入的文档数量

如果看一下funding_rounds数组,我们知道对于每个funding_rounds,都有一个raised_amount和一个funded_year字段。因此,unwind将为作为funding_rounds数组元素的每个文档生成一个输出文档。现在,在此示例中,我们的值为string。但是,不管数组中元素的值的类型如何,unwind都会为这些值中的每一个生成一个输出文档,因此所讨论的字段将仅具有该元素。在的情况下funding_rounds,该元素将成为这些文档之一,作为funding_rounds传递到我们project阶段的每个文档的值。然后,执行此操作的结果是,现在我们得到一个amount和一个year。一个用于每一轮融资,每家公司在我们的收藏中。这意味着我们的比赛产生了许多公司文件,而这些公司文件中的每个文件都产生了许多文件。每个公司文件中的每一轮融资都需要一个。unwind使用从match舞台移交给它的文档执行此操作。然后将每个公司的所有这些文档传递到project阶段。

放卷输出

因此,出资者为Greylock的所有文档(如查询示例中所示)将被拆分为多个文档,等于与筛选器匹配的每个公司的融资轮数$match: {"funding_rounds.investments.financial_org.permalink": "greylock" }。然后将这些结果文件中的每一个都传递给我们project。现在,unwind为它接收作为输入的每个文档生成一个精确的副本。所有字段都具有相同的键和值,但有一个例外,那就是该funding_rounds字段不是一个funding_rounds文档数组,而是一个单独文档的值,这是一轮单独的融资。因此,一家拥有4轮融资的公司将unwind创建4文件。其中每个字段都是精确的副本(funding_rounds字段除外),而不是每个副本的数组,而是字段中当前正在处理funding_rounds的公司文档中数组的单独元素unwind。因此,unwind输出到下一阶段的文件多于其收到的输入的效果。这意味着我们的project阶段现在funding_rounds再次获得一个不是数组的字段,而是一个包含raised_amount和的嵌套文档funded_year。因此,projectmatch通过过滤器为每个公司接收多个文档,因此可以分别处理每个文档,并为每个公司的每个融资回合确定单独的金额和年份


2
使用相同的文档会更好。
Jeb50 '17

1
作为$ unwind的第一个用例,我有非常复杂的嵌套集。在mongo文档和stackowerflow之间,您的回答终于使我更好地了解了$ project和$ unwind。谢谢@Zameer!
7

3

根据mongodb官方文档:

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

通过基本示例进行说明:

收集清单具有以下文档:

{ "_id" : 1, "item" : "ABC", "sizes": [ "S", "M", "L"] }
{ "_id" : 2, "item" : "EFG", "sizes" : [ ] }
{ "_id" : 3, "item" : "IJK", "sizes": "M" }
{ "_id" : 4, "item" : "LMN" }
{ "_id" : 5, "item" : "XYZ", "sizes" : null }

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

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

要么

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

以上查询输出:

{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }

为什么需要它?

$ unwind在执行聚合时非常有用。在执行各种操作(如排序,搜索等)之前,它将复杂/嵌套的文档分解为简单文档。

要了解有关$ unwind的更多信息:

https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

要了解有关聚合的更多信息:

https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/


2

考虑以下示例以了解集合中的此数据

{
        "_id" : 1,
        "shirt" : "Half Sleeve",
        "sizes" : [
                "medium",
                "XL",
                "free"
        ]
}

查询-db.test1.aggregate([{$ unwind:“ $ sizes”}]);

输出

{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "medium" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "XL" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "free" }

1

让我以与RDBMS方式相关的方式进行解释。这是声明:

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

申请文件/记录

{
 title : "this is my title" ,
 author : "bob" ,
 posted : new Date () ,
 pageViews : 5 ,
 tags : [ "fun" , "good" , "fun" ] ,
 comments : [
             { author :"joe" , text : "this is cool" } ,
             { author :"sam" , text : "this is bad" }
 ],
 other : { foo : 5 }
}

$项目/选择简单地返回这些字段/列,

文章中选择作者,标题,标签

接下来是Mongo的有趣的部分,请将此数组tags : [ "fun" , "good" , "fun" ]视为另一个相关的表(不能将其作为查找/引用表,因为值有些重复),称为“标签”。请记住,SELECT通常会垂直生成东西,因此展开“标签”就是将split()垂直拆分为表“标签”。

$ project + $ unwind的最终结果: 在此处输入图片说明

将输出转换为JSON:

{ "author": "bob", "title": "this is my title", "tags": "fun"},
{ "author": "bob", "title": "this is my title", "tags": "good"},
{ "author": "bob", "title": "this is my title", "tags": "fun"}

因为我们没有告诉Mongo忽略“ _id”字段,所以它是自动添加的。

关键是使它像表一样执行聚合。


或另一种思考方式是UNION ALL
Jeb50 '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.