Was ist der Operator $ unwind in MongoDB?

103

Dies ist mein erster Tag mit MongoDB, also mach es mir einfach :)

Ich kann den $unwindOperator nicht verstehen , vielleicht weil Englisch nicht meine Muttersprache ist.

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

Der Projektbetreiber ist etwas, das ich verstehen kann, nehme ich an (es ist wie SELECT, nicht wahr?). Dann gibt $unwind(unter Berufung) für jedes Mitglied des abgewickelten Arrays in jedem Quelldokument ein Dokument zurück .

Ist das wie ein JOIN? Wenn ja, wie das Ergebnis $project(mit _id, author, titleund tagsFelder) können mit dem verglichen werden tagsArray?

HINWEIS : Ich habe das Beispiel von der MongoDB-Website übernommen und kenne die Struktur des tagsArrays nicht. Ich denke, es ist eine einfache Reihe von Tag-Namen.

Gremo
quelle

Antworten:

234

Willkommen bei MongoDB!

Die Sache, an die Sie sich erinnern sollten, ist, dass MongoDB einen "NoSQL" -Ansatz für die Datenspeicherung verwendet, sodass Sie die Gedanken an Auswahlen, Verknüpfungen usw. aus Ihrem Kopf verlieren. Die Art und Weise, wie Ihre Daten gespeichert werden, erfolgt in Form von Dokumenten und Sammlungen, die ein dynamisches Mittel zum Hinzufügen und Abrufen der Daten von Ihren Speicherorten ermöglichen.

Um das Konzept hinter dem Parameter $ unwind zu verstehen, müssen Sie zunächst verstehen, was der Anwendungsfall, den Sie zitieren möchten, aussagt. Das Beispieldokument von mongodb.org lautet wie folgt:

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

Beachten Sie, dass Tags tatsächlich ein Array von 3 Elementen sind, in diesem Fall "Spaß", "Gut" und "Spaß".

Mit $ unwind können Sie für jedes Element ein Dokument abziehen und das resultierende Dokument zurückgeben. Um dies in einem klassischen Ansatz zu betrachten, wäre es gleichbedeutend mit "für jedes Element im Tags-Array ein Dokument mit nur diesem Element zurückgeben".

Somit ergibt sich folgendes Ergebnis:

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

würde die folgenden Dokumente zurückgeben:

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

Beachten Sie, dass sich im Ergebnisarray nur ändert, was im Tag-Wert zurückgegeben wird. Wenn Sie eine zusätzliche Referenz dazu benötigen, habe ich hier einen Link eingefügt . Hoffentlich hilft das und viel Glück bei Ihrem Streifzug durch eines der besten NoSQL-Systeme, die mir bisher begegnet sind.

HGS Labs
quelle
44

$unwind dupliziert jedes Dokument in der Pipeline einmal pro Array-Element.

Wenn Ihre Eingabe-Pipeline ein Artikeldokument mit zwei Elementen enthält tags, wird {$unwind: '$tags'}die Pipeline in zwei Artikeldokumente umgewandelt, die bis auf das tagsFeld identisch sind . Enthält im ersten Dokument tagsdas erste Element aus dem Array des Originaldokuments und im zweiten Dokument tagsdas zweite Element.

JohnnyHK
quelle
22

Lassen Sie es uns anhand eines Beispiels verstehen

So sieht das Unternehmensdokument aus:

Das echte Dokument

Dies $unwindermöglicht es uns, Dokumente als Eingabe mit einem Array-Wertfeld zu verwenden und Ausgabedokumente zu erstellen, sodass für jedes Element im Array ein Ausgabedokument vorhanden ist. Quelle

Die $ Unwind-Phase

Kehren wir also zu den Beispielen unserer Unternehmen zurück und werfen wir einen Blick auf die Verwendung von Abwicklungsstufen. Diese Abfrage:


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

Erstellt Dokumente mit Arrays für Betrag und Jahr.

Projektausgabe

Weil wir für jedes Element innerhalb des Finanzierungsrunden-Arrays auf den erhöhten Betrag und das finanzierte Jahr zugreifen. Um dies zu beheben, können wir eine Abwicklungsphase vor unserer Projektphase in diese Aggregationspipeline aufnehmen und dies parametrisieren, indem wir sagen, dass wir unwinddas Finanzierungsrunden-Array verwenden möchten :


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

Durch das Abwickeln werden mehr Dokumente in die nächste Stufe ausgegeben, als als Eingabe empfangen werden

Wenn wir uns das funding_roundsArray ansehen , wissen wir, dass es für jedes funding_roundsein raised_amountund ein funded_yearFeld gibt. Daher unwindwird für jedes der Dokumente, die Elemente des funding_roundsArrays sind, ein Ausgabedokument erstellt. In diesem Beispiel sind unsere Werte strings. Unabhängig von der Art des Werts für die Elemente in einem Array unwindwird jedoch für jeden dieser Werte ein Ausgabedokument erstellt, sodass das betreffende Feld genau dieses Element enthält. Im Fall von funding_roundsist dieses Element eines dieser Dokumente als Wert funding_roundsfür jedes Dokument, das an unsere projectBühne weitergegeben wird. Das Ergebnis, nachdem wir dies ausgeführt haben, ist, dass wir jetzt ein amountund ein bekommen year. Eine für jede Finanzierungsrunde für jedes Unternehmenin unserer Sammlung. Dies bedeutet, dass unser Match viele Unternehmensdokumente erstellt hat und jedes dieser Unternehmensdokumente zu vielen Dokumenten führt. Eine für jede Finanzierungsrunde in jedem Unternehmensdokument. unwindführt diesen Vorgang mit den Dokumenten durch, die ihm von der matchBühne übergeben wurden. Und all diese Dokumente für jedes Unternehmen werden dann auf die projectBühne gebracht.

Ausgabe abwickeln

Daher werden alle Dokumente, bei denen der Geldgeber Greylock war (wie im Abfragebeispiel), in mehrere Dokumente aufgeteilt, die der Anzahl der Finanzierungsrunden für jedes Unternehmen entsprechen, das dem Filter entspricht $match: {"funding_rounds.investments.financial_org.permalink": "greylock" }. Und jedes dieser resultierenden Dokumente wird dann an unsere weitergegeben project. Erstellt jetzt unwindeine exakte Kopie für jedes der Dokumente, die es als Eingabe erhält. Alle Felder haben den gleichen Schlüssel und Wert, mit einer Ausnahme, und das heißt, dass das funding_roundsFeld nicht ein Array von funding_roundsDokumenten ist, sondern einen Wert, der ein einzelnes Dokument ist, bei dem es sich um eine einzelne Finanzierungsrunde handelt. Ein Unternehmen mit 4 Finanzierungsrunden führt also zur unwindSchaffung von 4Unterlagen. Wobei jedes Feld eine exakte Kopie ist, mit Ausnahme des funding_roundsFelds, das anstelle eines Arrays für jede dieser Kopien ein einzelnes Element aus dem funding_roundsArray des unwindderzeit verarbeiteten Unternehmensdokuments ist. Dies unwindhat zur Folge, dass mehr Dokumente in die nächste Stufe ausgegeben werden, als als Eingabe empfangen werden. Das bedeutet, dass unsere projectBühne jetzt ein funding_roundsFeld erhält , das wiederum kein Array ist, sondern ein verschachteltes Dokument mit einem raised_amountund einem funded_yearFeld. So projectwird für jedes Unternehmen mehrere Dokumente erhalten matchdie Filter ing und kann daher jedes der Dokumente verarbeiten individuell und identifizieren eine individuelle Menge und Jahr für jedes Finanzierungsrunde für jedes Unternehmen.

Zameer
quelle
2
Die Verwendung des gleichen Dokuments ist besser.
Jeb50
1
Als ersten Anwendungsfall für $ unwind hatte ich eine ziemlich komplizierte verschachtelte Menge verschachtelter Mengen. Ihre Antwort zwischen Mongo Docs und Stackowerflow hat mir schließlich geholfen, $ project zu verstehen und $ besser abzuwickeln. Danke @Zameer!
sieben
3

Gemäß der offiziellen Dokumentation von Mongodb:

$ unwind Dekonstruiert ein Array-Feld aus den Eingabedokumenten, um für jedes Element ein Dokument auszugeben. Jedes Ausgabedokument ist das Eingabedokument, wobei der Wert des Arrayfelds durch das Element ersetzt wird.

Erklärung anhand eines einfachen Beispiels:

Ein Sammlungsinventar enthält die folgenden Dokumente:

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

Die folgenden $ Abroll - Operationen sind gleichwertig und ein Dokument für jedes Element in dem Rück Größen Feld. Wenn das Größenfeld nicht in ein Array aufgelöst wird, aber nicht fehlt, null oder ein leeres Array, behandelt $ unwind den Nicht-Array-Operanden als ein einzelnes Elementarray.

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

oder

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

Über der Abfrageausgabe:

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

Warum wird es benötigt?

$ unwind ist sehr nützlich, wenn Sie eine Aggregation durchführen. Es zerlegt komplexe / verschachtelte Dokumente in einfache Dokumente, bevor verschiedene Vorgänge wie Sortieren, Suchen usw. ausgeführt werden.

Um mehr über $ unwind zu erfahren:

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

Um mehr über die Aggregation zu erfahren:

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

Amitesh Bharti
quelle
2

Betrachten Sie das folgende Beispiel, um diese Daten in einer Sammlung zu verstehen

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

Abfrage - db.test1.aggregate ([{$ unwind: "$ size"}]);

Ausgabe

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

Lassen Sie mich auf eine Weise erklären, die mit der RDBMS-Art korreliert. Dies ist die Aussage:

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

auf das Dokument / den Datensatz anwenden :

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

Das $ project / Select gibt einfach diese Felder / Spalten als zurück

SELECT Autor, Titel, Tags FROM Artikel

Als nächstes folgt der unterhaltsame Teil von Mongo. Betrachten Sie dieses Array tags : [ "fun" , "good" , "fun" ]als eine andere verwandte Tabelle (kann keine Nachschlagetabelle / Referenztabelle sein, da Werte einige Duplikate aufweisen) mit dem Namen "tags". Denken Sie daran, dass SELECT im Allgemeinen vertikale Elemente erzeugt. Wenn Sie also die "Tags" abwickeln, teilen Sie () vertikal in "Tags" der Tabelle auf.

Das Endergebnis von $ project + $ unwind: Geben Sie hier die Bildbeschreibung ein

Übersetzen Sie die Ausgabe in 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"}

Da wir Mongo nicht angewiesen haben, das Feld "_id" wegzulassen, wird es automatisch hinzugefügt.

Der Schlüssel besteht darin, die Aggregation tabellenartig zu gestalten.

Jeb50
quelle
Oder eine andere Art, darüber nachzudenken, ist UNION ALL
Jeb50