Co to jest operator $ relax w MongoDB?


103

To mój pierwszy dzień z MongoDB, więc proszę, nie wahaj się :)

Nie rozumiem $unwindoperatora, może dlatego, że angielski nie jest moim językiem ojczystym.

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

Przypuszczam, że operator projektu jest czymś, co rozumiem (to tak SELECT, prawda?). Ale potem $unwind(cytowanie) zwraca jeden dokument na każdego członka odwiniętej tablicy w każdym dokumencie źródłowym .

Czy to jest jak JOIN? Jeśli tak, to jak wynika z $project(z _id, author, titlei tagspól) można porównać z tagstablicy?

UWAGA : wziąłem przykład ze strony MongoDB, nie znam struktury tagstablicy. Myślę, że to prosta tablica nazw znaczników.

Odpowiedzi:


237

Po pierwsze, witamy w MongoDB!

Należy pamiętać, że MongoDB stosuje podejście „NoSQL” do przechowywania danych, więc zapomnij o myślach o wyborze, złączeniach itp. Sposób, w jaki przechowuje Twoje dane, ma postać dokumentów i zbiorów, co pozwala na dynamiczne dodawanie i pozyskiwanie danych z Twoich lokalizacji przechowywania.

Biorąc to pod uwagę, aby zrozumieć koncepcję parametru $ relax, musisz najpierw zrozumieć, co mówi przypadek użycia, który próbujesz zacytować. Przykładowy dokument z mongodb.org wygląda następująco:

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

Zwróć uwagę, że tagi są w rzeczywistości tablicą trzech elementów, w tym przypadku „zabawnych”, „dobrych” i „zabawnych”.

To, co robi $ relax, to pozwala ci zdjąć dokument dla każdego elementu i zwrócić ten wynikowy dokument. Myślenie o tym w klasycznym podejściu byłoby równoznaczne z wyrażeniem „dla każdego elementu tablicy tagów zwróć dokument zawierający tylko ten element”.

W ten sposób wynik uruchomienia:

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

zwróci następujące dokumenty:

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

Zauważ, że jedyną zmianą w tablicy wyników jest to, co jest zwracane w wartości tagów. Jeśli potrzebujesz dodatkowych informacji na temat tego, jak to działa, zamieściłem tutaj link . Mam nadzieję, że to pomoże i powodzenia w wyprawie do jednego z najlepszych systemów NoSQL, z jakimi się do tej pory spotkałem.


44

$unwind powiela każdy dokument w potoku, raz na element tablicy.

Więc jeśli twój potok wejściowy zawiera jeden dokument artykułu z dwoma elementami w tags, {$unwind: '$tags'}przekształci potok w dwa dokumenty artykułu, które są takie same, z wyjątkiem tagspola. W pierwszym dokumencie tagsbędzie zawierał pierwszy element z tablicy oryginalnego dokumentu, aw drugim dokumencie tagsbędzie zawierał drugi element.


22

Zrozummy to na przykładzie

Tak wygląda dokument firmowy :

orginalny dokument

$unwindPozwala nam wziąć dokumenty jako wejście, które mają pole wartościową tablicy i tworzy dokumenty wyjściowe, takie, że istnieje jeden dokument wyjściowy dla każdego elementu w tablicy. źródło

Etap $ odprężenia

Wróćmy więc do przykładów naszych firm i przyjrzyjmy się zastosowaniu etapów odprężania. To zapytanie:


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

tworzy dokumenty zawierające tablice dla kwoty i roku.

wyniki projektu

Ponieważ uzyskujemy dostęp do zebranej kwoty i finansowanego roku dla każdego elementu w tablicy rund finansowania. Aby to naprawić, możemy uwzględnić etap rozwijania przed etapem naszego projektu w tym potoku agregacji i sparametryzować to, mówiąc, że chcemy, aby unwindtablica rund finansowania:


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

Rozwinięcie skutkuje wyprowadzeniem do następnego etapu większej liczby dokumentów, niż otrzymuje jako dane wejściowe

Jeśli spojrzymy na funding_roundstablicę, wiemy, że dla każdego funding_roundsistnieje pole raised_amounti funded_yearpole. Tak więc, unwinddla każdego z dokumentów, które są elementami funding_roundstablicy, utworzy dokument wyjściowy. W tym przykładzie nasze wartości to strings. Ale niezależnie od typu wartości elementów w tablicy, unwindstworzy dokument wyjściowy dla każdej z tych wartości, tak że dane pole będzie miało tylko ten element. W przypadku funding_rounds, ten element będzie jednym z tych dokumentów jako wartość funding_roundsdla każdego dokumentu, który zostanie przekazany na nasz projectetap. W rezultacie po uruchomieniu tego otrzymujemy teraz an amounti a year. Jedna na każdą rundę finansowania dla każdej firmyw naszej kolekcji. Oznacza to, że nasze dopasowanie wygenerowało wiele dokumentów firmowych, a każdy z tych dokumentów firmowych skutkuje wieloma dokumentami. Jedna na każdą rundę finansowania w ramach każdego dokumentu firmowego. unwindwykonuje tę operację korzystając z dokumentów przekazanych jej ze matchsceny. Wszystkie te dokumenty dla każdej firmy są następnie przekazywane na projectscenę.

rozwijanie wyjścia

Tak więc wszystkie dokumenty, w których fundatorem był Greylock (jak w przykładzie zapytania), zostaną podzielone na kilka dokumentów, równą liczbie rund finansowania dla każdej firmy pasującej do filtra $match: {"funding_rounds.investments.financial_org.permalink": "greylock" }. Każdy z tych dokumentów zostanie następnie przekazany do naszego project. Teraz unwindtworzy dokładną kopię dla każdego dokumentu, który otrzymuje jako dane wejściowe. Wszystkie pola mają ten sam klucz i wartość, z jednym wyjątkiem, a mianowicie, że funding_roundspole zamiast być tablicą funding_roundsdokumentów, zamiast tego ma wartość będącą pojedynczym dokumentem, który jest indywidualną rundą finansowania. Tak więc firma, która ma 4 rundy finansowania, spowoduje unwindutworzenie 4dokumenty. Gdzie każde pole jest dokładną kopią, z wyjątkiem funding_roundspola, które zamiast być tablicą dla każdej z tych kopii, będzie pojedynczym elementem funding_roundstablicy z dokumentu firmy, który unwindjest obecnie przetwarzany. Tak więc, unwindskutkuje wyprowadzeniem do następnego etapu większej liczby dokumentów, niż otrzymuje jako dane wejściowe. Oznacza to, że nasza projectscena otrzymuje teraz funding_roundspole, które znowu nie jest tablicą, ale jest zagnieżdżonym dokumentem, który ma a raised_amounti funded_yearpole. W związku z projecttym otrzyma wiele dokumentów dla każdej firmy matchz filtrem i dlatego może przetwarzać każdy z dokumentów indywidualnie i określać indywidualną kwotę i rok dla każdej rundy finansowania dla każdej firmy.


2
użycie tego samego dokumentu będzie lepsze.
Jeb50

1
Jako pierwszy przypadek użycia $ relax miałem dość skomplikowany zestaw zagnieżdżonych zestawów. Przechodząc między mongo docs i stackowerflow, twoja odpowiedź w końcu pomogła mi lepiej zrozumieć $ project i $ relax. Dzięki @Zameer!
siódma

3

Zgodnie z oficjalną dokumentacją Mongodb:

$ relax Dekonstruuje pole tablicowe z dokumentów wejściowych, aby wyprowadzić dokument dla każdego elementu. Każdy dokument wyjściowy jest dokumentem wejściowym, w którym wartość pola tablicy jest zastępowana przez element.

Wyjaśnienie poprzez podstawowy przykład:

Na inwentarz zbiorczy znajdują się następujące dokumenty:

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

Następujące operacje $ rozwijania są równoważne i zwracają dokument dla każdego elementu w polu rozmiarów . Jeśli pole rozmiarów nie jest przekształcane w tablicę, ale nie brakuje, ma wartości null lub jest pusta, $ relax traktuje operand niebędący tablicą jako tablicę pojedynczego elementu.

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

lub

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

Powyższe dane wyjściowe zapytania:

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

Dlaczego jest to potrzebne?

$ relax jest bardzo przydatne podczas wykonywania agregacji. dzieli złożony / zagnieżdżony dokument na prosty dokument przed wykonaniem różnych operacji, takich jak sortowanie, wyszukiwanie itp.

Aby dowiedzieć się więcej o $ relax:

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

Aby dowiedzieć się więcej o agregacji:

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


2

rozważ poniższy przykład, aby zrozumieć te dane w kolekcji

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

Zapytanie - db.test1.aggregate ([{$ odwijanie: "$ rozmiary"}]);

wynik

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

1

Pozwólcie, że wyjaśnię w sposób powiązany z RDBMS. Oto oświadczenie:

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

złożyć wniosek do dokumentu / zapisu :

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

$ Projekt / Select prostu zwraca te kolumny jak ostrości /

SELECT autor, tytuł, tagi Z artykułu

Następna jest fajna część Mongo, traktuj tę tablicę tags : [ "fun" , "good" , "fun" ]jako kolejną powiązaną tabelę (nie może być tabelą przeglądową / referencyjną, ponieważ wartości są zduplikowane) o nazwie „tagi”. Pamiętaj, że SELECT generalnie tworzy rzeczy pionowe, więc rozwinięcie "tagów" to podzielenie () pionowo na "tagi" tabeli.

Końcowy wynik $ project + $ relax: wprowadź opis obrazu tutaj

Przetłumacz wynik na 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"}

Ponieważ nie kazaliśmy Mongo pominąć pola „_id”, więc jest ono dodawane automatycznie.

Najważniejsze jest, aby przeprowadzanie agregacji było podobne do tabeli.


Innym sposobem myślenia o tym jest UNION ALL
Jeb50,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.