Výše uvedený dotaz vrací dokumenty, které "téměř" odpovídají User dokumenty, ale mají také příspěvky jednotlivých uživatelů. Takže v podstatě výsledkem je řada User dokumenty s Post pole nebo řez vložený .
Jedním ze způsobů by bylo přidat Posts []*Post do pole User a měli bychom hotovo:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
I když to funguje, zdá se "přehnané" rozšířit User s Posts jen kvůli jedinému dotazu. Pokud bychom pokračovali touto cestou, náš User typ by se naplnil spoustou „extra“ polí pro různé dotazy. Nemluvě o tom, jestli vyplňujeme Posts pole a uložit uživatele, tyto příspěvky by skončily uloženy uvnitř User dokument. Ne to, co chceme.
Dalším způsobem by bylo vytvořit UserWithPosts zadejte kopírování User a přidáním Posts []*Post pole. Netřeba dodávat, že je to ošklivé a neflexibilní (jakékoli změny provedené v User by se muselo projevit v UserWithPosts ručně).
Se strukturovaným vkládáním
Místo úpravy původního User a místo vytvoření nového UserWithPosts zadejte od začátku, můžeme použít vkládání struktur
(opětovné použití stávajícího User a Post typy) s malým trikem:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Všimněte si hodnoty značky bson
",inline" . To je zdokumentováno na bson.Marshal()
a bson.Unmarshal()
(použijeme jej pro unmarshaling):
Pomocí vkládání a ",inline" hodnota značky, UserWithPosts samotný typ bude platným cílem pro odstranění User dokumenty a jeho Post []*Post pole bude perfektní volbou pro vyhledané "posts" .
Použití:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Nebo získání všech výsledků v jednom kroku:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
Deklarace typu UserWithPosts může nebo nemusí být místní deklarací. Pokud to jinde nepotřebujete, může to být lokální deklarace ve funkci, kde spouštíte a zpracováváte agregační dotaz, takže nenadmou vaše stávající typy a deklarace. Pokud jej chcete znovu použít, můžete jej deklarovat na úrovni balíčku (exportovaný nebo neexportovaný) a používat jej, kdekoli jej potřebujete.
Úprava agregace
Další možností je použít $replaceRoot MongoDB
k "přeuspořádání" výsledných dokumentů, takže "jednoduchá" struktura dokonale pokryje dokumenty:
// Query users with their posts:
pipe := collUsers.Pipe([]bson.M{
{
"$lookup": bson.M{
"from": "posts",
"localField": "_id",
"foreignField": "userID",
"as": "posts",
},
},
{
"$replaceRoot": bson.M{
"newRoot": bson.M{
"user": "$$ROOT",
"posts": "$posts",
},
},
},
})
S tímto přemapováním lze výsledné dokumenty modelovat takto:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Všimněte si, že zatímco to funguje, posts pole všech dokumentů bude načteno ze serveru dvakrát:jednou jako posts pole vrácených dokumentů a jednou jako pole user; nemapujeme / nepoužíváme, ale je přítomen ve výsledných dokumentech. Pokud tedy zvolíte toto řešení, user.posts pole by mělo být odstraněno např. s $project fáze:
pipe := collUsers.Pipe([]bson.M{
{
"$lookup": bson.M{
"from": "posts",
"localField": "_id",
"foreignField": "userID",
"as": "posts",
},
},
{
"$replaceRoot": bson.M{
"newRoot": bson.M{
"user": "$$ROOT",
"posts": "$posts",
},
},
},
{"$project": bson.M{"user.posts": 0}},
})