Quel est l'opérateur $ unwind dans MongoDB?

103

C'est mon premier jour avec MongoDB alors allez-y doucement avec moi :)

Je ne comprends pas l' $unwindopérateur, peut-être parce que l'anglais n'est pas ma langue maternelle.

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

L'opérateur de projet est quelque chose que je peux comprendre, je suppose (c'est comme SELECT, n'est-ce pas?). Mais alors, $unwind(citant) retourne un document pour chaque membre du tableau déroulé dans chaque document source .

Est-ce que c'est comme un JOIN? Si oui, comment le résultat de $project(avec _id, author, titleet les tagschamps) peuvent être comparés avec le tagstableau?

REMARQUE : j'ai pris l'exemple du site Web MongoDB, je ne connais pas la structure du tagstableau. Je pense que c'est un simple tableau de noms de balises.

gremo
la source

Réponses:

236

Tout d'abord, bienvenue sur MongoDB!

La chose à retenir est que MongoDB emploie une approche «NoSQL» pour le stockage des données, donc disparaissent de votre esprit les pensées de sélection, de jointure, etc. La façon dont il stocke vos données se présente sous la forme de documents et de collections, ce qui permet un moyen dynamique d'ajouter et d'obtenir les données de vos emplacements de stockage.

Cela étant dit, afin de comprendre le concept derrière le paramètre $ unwind, vous devez d'abord comprendre ce que dit le cas d'utilisation que vous essayez de citer. L'exemple de document de mongodb.org est le suivant:

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

Remarquez que les balises sont en fait un tableau de 3 éléments, dans ce cas étant "fun", "good" et "fun".

Ce que fait $ unwind, c'est vous permettre de décoller un document pour chaque élément et de renvoyer ce document résultant. Pour penser à cela dans une approche classique, ce serait l'équivalent de "pour chaque élément du tableau de balises, renvoyer un document avec uniquement cet élément".

Ainsi, le résultat de l'exécution de ce qui suit:

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

renverrait les documents suivants:

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

Notez que la seule chose qui change dans le tableau de résultats est ce qui est renvoyé dans la valeur des balises. Si vous avez besoin d'une référence supplémentaire sur la façon dont cela fonctionne, j'ai inclus un lien ici . J'espère que cela vous aidera, et bonne chance pour votre incursion dans l'un des meilleurs systèmes NoSQL que j'ai rencontrés jusqu'à présent.

Laboratoires HGS
la source
44

$unwind duplique chaque document du pipeline, une fois par élément de tableau.

Ainsi, si votre pipeline d'entrée contient un document d'article avec deux éléments tags, {$unwind: '$tags'}le pipeline sera transformé en deux documents d'article identiques à l'exception du tagschamp. Dans le premier document, tagscontiendrait le premier élément du tableau du document d'origine, et dans le deuxième document, tagscontiendrait le deuxième élément.

JohnnyHK
la source
22

Comprenons-le par un exemple

Voici à quoi ressemble le document d' entreprise :

document original

Le $unwindnous permet de prendre des documents en entrée qui ont un champ de valeur de tableau et de produire des documents de sortie, de sorte qu'il y ait un document de sortie pour chaque élément du tableau. la source

L'étape $ dérouler

Revenons donc aux exemples de nos entreprises et examinons l'utilisation des étapes de déroulement. Cette requête:


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

produit des documents qui ont des tableaux pour le montant et l'année.

sortie du projet

Parce que nous accédons au montant levé et à l'année de financement pour chaque élément du tableau des rondes de financement. Pour résoudre ce problème, nous pouvons inclure une étape de déroulement avant l'étape de notre projet dans ce pipeline d'agrégation, et paramétrer cela en disant que nous voulons unwindle tableau des tours de financement:


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

dérouler a pour effet de produire à l'étape suivante plus de documents qu'il n'en reçoit en entrée

Si nous regardons le funding_roundstableau, nous savons que pour chacun funding_rounds, il y a un raised_amountet un funded_yearchamp. Ainsi, unwindpour chacun des documents qui sont des éléments du funding_roundstableau, produira un document de sortie. Maintenant, dans cet exemple, nos valeurs sont strings. Mais, quel que soit le type de valeur pour les éléments d'un tableau, unwindproduira un document de sortie pour chacune de ces valeurs, de sorte que le champ en question aura juste cet élément. Dans le cas de funding_rounds, cet élément sera l'un de ces documents en tant que valeur funding_roundspour chaque document transmis à notre projectétape. Le résultat, après avoir exécuté ceci, est que maintenant nous obtenons un amountet un year. Un pour chaque cycle de financement pour chaque entreprisedans notre collection. Cela signifie que notre correspondance a produit de nombreux documents d'entreprise et que chacun de ces documents d'entreprise aboutit à de nombreux documents. Un pour chaque cycle de financement dans chaque document d'entreprise. unwindeffectue cette opération à l'aide des documents qui lui sont remis depuis la matchscène. Et tous ces documents pour chaque entreprise sont ensuite passés à l' projectétape.

dérouler la sortie

Ainsi, tous les documents dont le bailleur de fonds était Greylock (comme dans l'exemple de requête) seront divisés en un nombre de documents, égal au nombre de tours de financement pour chaque entreprise qui correspond au filtre $match: {"funding_rounds.investments.financial_org.permalink": "greylock" }. Et chacun de ces documents sera ensuite transmis à notre project. Maintenant, unwindproduit une copie exacte pour chacun des documents qu'il reçoit en entrée. Tous les champs ont la même clé et la même valeur, à une exception près, à savoir que le funding_roundschamp plutôt que d'être un tableau de funding_roundsdocuments, a plutôt une valeur qui est un document unique, qui est un tour de financement individuel. Ainsi, une entreprise qui dispose de 4 tours de financement se traduira par la unwindcréation de 4documents. Où chaque champ est une copie exacte, à l'exception du funding_roundschamp, qui, au lieu d'être un tableau pour chacune de ces copies, sera à la place un élément individuel du funding_roundstableau du document d'entreprise en unwindcours de traitement. Donc, unwinda pour effet de sortir à l'étape suivante plus de documents qu'il n'en reçoit en entrée. Cela signifie que notre projectscène obtient maintenant un funding_roundschamp qui, encore une fois, n'est pas un tableau, mais plutôt un document imbriqué qui a un raised_amountet un funded_yearchamp. Ainsi, projectrecevra plusieurs documents pour chaque entreprise matchdu filtre et pourra donc traiter chacun des documents individuellement et identifier un montant et une année individuels pour chaque tour de financement pour chaque entreprise..

Zameer Ansari
la source
2
utiliser le même document sera mieux.
Jeb50
1
Comme premier cas d'utilisation de $ unwind, j'avais un ensemble d'ensembles imbriqués assez compliqués. Entre mongo docs et stackowerflow, votre réponse m'a finalement aidé à mieux comprendre $ project et $ unwind. Merci @Zameer!
sept
3

Selon la documentation officielle de mongodb:

$ unwind Déconstruit un champ de tableau à partir des documents d'entrée pour générer un document pour chaque élément. Chaque document de sortie est le document d'entrée avec la valeur du champ de tableau remplacée par l'élément.

Explication par l'exemple de base:

Un inventaire de collection contient les documents suivants:

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

Les opérations $ unwind suivantes sont équivalentes et renvoient un document pour chaque élément du champ des tailles . Si le champ des tailles ne se résout pas en un tableau mais n'est pas manquant, nul ou vide, $ unwind traite l'opérande non-tableau comme un tableau à un seul élément.

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

ou

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

Au-dessus de la sortie de la requête:

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

Pourquoi est-ce nécessaire?

$ unwind est très utile lors de l'agrégation. il divise un document complexe / imbriqué en un document simple avant d'effectuer diverses opérations telles que le tri, la recherche, etc.

Pour en savoir plus sur $ dérouler:

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

Pour en savoir plus sur l'agrégation:

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

Amitesh Bharti
la source
2

considérez l'exemple ci-dessous pour comprendre ces données dans une collection

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

Requête - db.test1.aggregate ([{$ unwind: "$ tailles"}]);

production

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

Permettez-moi de vous expliquer d'une manière corrélée au SGBDR. Voici la déclaration:

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

à appliquer au document / enregistrement :

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

Le $ project / Select renvoie simplement ces champs / colonnes comme

SELECT auteur, titre, balises FROM article

Vient ensuite la partie amusante de Mongo, considérez ce tableau tags : [ "fun" , "good" , "fun" ]comme une autre table associée (ne peut pas être une table de recherche / référence car les valeurs ont une certaine duplication) nommée "tags". Rappelez-vous que SELECT produit généralement des choses verticales, donc dérouler les "balises" consiste à diviser () verticalement en "balises" de table.

Le résultat final de $ project + $ unwind: entrez la description de l'image ici

Traduisez la sortie en 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"}

Parce que nous n'avons pas dit à Mongo d'omettre le champ "_id", il est donc ajouté automatiquement.

La clé est de le rendre semblable à une table pour effectuer une agrégation.

Jeb50
la source
Ou une autre façon de penser est UNION ALL
Jeb50