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}},
})