Что такое оператор $ unwind в MongoDB?

103

Это мой первый день работы с MongoDB, так что не торопитесь со мной :)

Я не понимаю $unwindоператора, может потому, что английский не мой родной язык.

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

Оператор проекта - это то, что я могу понять, я полагаю (это как SELECT, не так ли?). Но затем $unwind(цитирование) возвращает один документ для каждого члена развернутого массива в каждом исходном документе .

Это похоже на JOIN? Если да, то как результат $project_id, author, titleи tagsполя) можно сравнить с tagsмассивом?

ПРИМЕЧАНИЕ : я взял пример с сайта MongoDB, я не знаю структуру tagsмассива. Я думаю, это простой массив имен тегов.

Gremo
источник

Ответы:

240

Прежде всего, добро пожаловать в 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 элементов, в данном случае «забавный», «хороший» и «забавный».

$ 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, с которыми я когда-либо сталкивался.

HGS Labs
источник
44

$unwind дублирует каждый документ в конвейере один раз для каждого элемента массива.

Поэтому, если ваш входной конвейер содержит один документ статьи с двумя элементами внутри tags, {$unwind: '$tags'}конвейер будет преобразован в два документа статей, которые идентичны, за исключением tagsполя. В первом документе tagsбудет содержаться первый элемент из исходного массива документа, а во втором документе tags- второй элемент.

JohnnyHK
источник
22

Разберемся на примере

Вот так выглядит документ компании :

оригинальный документ

$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массива, создать выходной документ. Теперь, в этом примере, наши значения - strings. Но, независимо от типа значения для элементов в массиве, 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полями и. Таким образом, он projectбудет получать несколько документов для каждой компании, matchвключенной в фильтр, и поэтому может обрабатывать каждый из документов индивидуально и определять индивидуальную сумму и год для каждого раунда финансирования для каждой компании..

Замир Ансари
источник
2
использование того же документа будет лучше.
Jeb50 09
1
В качестве первого варианта использования $ unwind у меня был довольно сложный вложенный набор вложенных наборов. Переходя между документами mongo и stackowerflow, ваш ответ, наконец, помог мне лучше понять $ project и $ раскручиваться. Спасибо, @Zameer!
семь,
3

Согласно официальной документации mongodb:

$ unwind Деконструирует поле массива из входных документов для вывода документа для каждого элемента. Каждый выходной документ - это входной документ, в котором значение поля массива заменено элементом.

Объяснение на базовом примере:

В инвентарной описи есть следующие документы:

{ "_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 эквивалентны и возвращают документ для каждого элемента в поле размеров . Если поле размеров не преобразуется в массив, но не отсутствует, имеет значение 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

Позвольте мне объяснить, как это связано с РСУБД. Это заявление:

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 }
}

$ Проект / Select просто возвращает эти поля / столбцы как

ВЫБЕРИТЕ автора, заголовок, теги ИЗ статьи

Далее следует интересная часть Mongo: рассматривайте этот массив tags : [ "fun" , "good" , "fun" ]как другую связанную таблицу (не может быть таблицей поиска / справки, потому что значения имеют некоторое дублирование) с именем «теги». Помните, что SELECT обычно производит вещи вертикально, поэтому раскрутить «теги» означает разделить () вертикально на «теги» таблицы.

Конечный результат $ 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», поэтому оно добавляется автоматически.

Ключ в том, чтобы сделать агрегацию похожей на таблицу.

Jeb50
источник
Или еще один способ думать об этом - UNION ALL
Jeb50,