Що таке оператор $ unind у 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масиву. Я думаю, що це простий масив імен тегів.

Відповіді:


236

По-перше, ласкаво просимо до MongoDB!

Що слід пам’ятати, це те, що MongoDB використовує «NoSQL» підхід до зберігання даних, тож зникають думки про вибір, приєднання тощо. Спосіб зберігання ваших даних складається у формі документів та колекцій, що дозволяє динамічно створювати засоби додавання та отримання даних зі своїх місць зберігання.

Однак, щоб зрозуміти поняття, що стоїть за параметром $ unind, спочатку ви повинні зрозуміти, про що йдеться у випадку використання, який ви намагаєтесь процитувати. Приклад документа з 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-х елементів, у цьому випадку "весело", "добре" та "весело".

Що робиться $ unind, це дозволяє вам зняти документ для кожного елемента і повернути отриманий документ. Щоб думати про це за класичним підходом, було б рівнозначно "для кожного елемента в масиві тегів повернути документ лише з цим елементом".

Таким чином, результат запуску наступний:

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поля. У першому doc tagsмістив би перший елемент з початкового масиву doc, а у другому doc tagsмістив би другий елемент.


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є a raised_amountі funded_yearполе. Отже, unwindбуде для кожного з документів, що є елементами funding_roundsмасиву, виводиться вихідний документ. Тепер у цьому прикладі наші значення - strings. Але, незалежно від типу значення для елементів масиву, unwindбуде видаватися вихідний документ для кожного з цих значень, таким чином, що у відповідному полі буде саме цей елемент. У випадку з funding_roundsцим елементом буде один із цих документів як значення funding_roundsдля кожного документа, який передається на наш projectетап. Результатом, а потім запустити це, є те, що тепер ми отримаємо amountі а year. По одному на кожен раунд фінансування для кожної компаніїв нашій колекції. Це означає, що наш матч видав багато документів компанії, і кожен з цих документів компанії приводить до багатьох документів. По одному на кожен раунд фінансування в рамках кожного документа компанії. unwindвиконує цю операцію, використовуючи документи, передані їй зі matchсцени. І всі ці документи для кожної компанії потім передаються на projectсцену.

розмотати вихід

Таким чином, усі документи, де засновником був Грейлок (як у прикладі запиту), будуть розділені на ряд документів, рівних кількості раундів фінансування для кожної компанії, яка відповідає фільтру $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

1
Як перший випадок використання $ unind у мене був досить складний вкладений набір вкладених наборів. Переходячи між документами mongo та stackowerflow, ваша відповідь, нарешті, допомогла мені зрозуміти $ project та $ undind краще. Дякую @Zameer!
сім

3

Відповідно до офіційної документації mongodb:

$ unind Деконструює поле масиву з вхідних документів для виведення документа для кожного елемента. Кожен вихідний документ є вхідним документом зі значенням поля масиву, заміненого елементом.

Пояснення через основний приклад:

Колекційний інвентар має такі документи:

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

Наступні операції $ unind еквівалентні і повертають документ для кожного елемента в полі розмірів . Якщо поле розмірів не відповідає масиву, але не відсутнє, null або порожній масив, $ unind розглядає операнд без масиву як масив одного елемента.

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

Для чого це потрібно?

$ unind дуже корисно під час виконання агрегації. він розбиває складний / вкладений документ на простий документ перед виконанням різних операцій, таких як сортування, пошук тощо.

Щоб дізнатися більше про $ unind:

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 ([{$ unind: "$ size"}]);

вихід

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

$ Проект / Select просто повертає ці поля / стовпці як

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

Далі йде забавна частина Монго, розгляньте цей масив tags : [ "fun" , "good" , "fun" ]як іншу пов’язану таблицю (не може бути таблицею пошуку / довідки, оскільки значення мають деяке дублювання) під назвою "теги". Пам'ятайте, що SELECT загалом створює речі вертикальні, тому розкручуйте "теги" - це split () вертикально на "теги" таблиці.

Кінцевий результат $ project + $ unind: введіть тут опис зображення

Переведіть вихід у 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"}

Тому що ми не сказали Монго опускати поле "_id", тому воно додається автоматично.

Ключ полягає в тому, щоб зробити його подібним до таблиці, щоб виконувати агрегацію.


Або інший спосіб подумати про це - UNION ALL
Jeb50
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.