sql >> Base de Datos >  >> NoSQL >> MongoDB

Agregación de Mgo:¿cómo reutilizar tipos de modelos para consultar y descomponer resultados mixtos?

La consulta anterior devuelve documentos que "casi" coinciden con User documentos, pero también tienen las publicaciones de cada usuario. Básicamente, el resultado es una serie de User documentos con una Post matriz o segmento incrustado .

Una forma sería agregar un Posts []*Post campo al User en sí mismo, y habríamos terminado:

type User struct {
    ID         string    `bson:"_id"`
    Name       string    `bson:"name"`
    Registered time.Time `bson:"registered"`
    Posts      []*Post   `bson:"posts,omitempty"`
}

Si bien esto funciona, parece "exagerado" extender User con Posts solo por el bien de una sola consulta. Si continuáramos por este camino, nuestro User type se hincharía con muchos campos "adicionales" para diferentes consultas. Ni hablar si llenamos las Posts y guarde al usuario, esas publicaciones terminarían guardadas dentro del User documento. No es lo que queremos.

Otra forma sería crear un UserWithPosts escriba copiando User y agregar Posts []*Post campo. No hace falta decir que esto es feo e inflexible (cualquier cambio realizado en User tendría que reflejarse en UserWithPosts manualmente).

Con incrustación de estructura

En lugar de modificar el User original , y en lugar de crear un nuevo UserWithPosts escriba desde "cero", podemos utilizar incrustación de estructuras (reutilizando el User existente y Post tipos) con un pequeño truco:

type UserWithPosts struct {
    User  `bson:",inline"`
    Posts []*Post `bson:"posts"`
}

Tenga en cuenta el valor de la etiqueta bson ",inline" . Esto está documentado en bson.Marshal() y bson.Unmarshal() (lo usaremos para desarmar):

Al usar la incrustación y el ",inline" valor de la etiqueta, el UserWithPosts type en sí mismo será un objetivo válido para desarmar User documentos y su Post []*Post será una elección perfecta para las "posts" buscadas .

Usándolo:

var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
    // Use uwp:
    fmt.Println(uwp)
}
// Handle it.Err()

O obtener todos los resultados en un solo paso:

var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error

La declaración de tipo de UserWithPosts puede ser o no una declaración local. Si no lo necesita en otro lugar, puede ser una declaración local en la función donde ejecuta y procesa la consulta de agregación, por lo que no inflará sus tipos y declaraciones existentes. Si desea reutilizarlo, puede declararlo a nivel de paquete (exportado o no exportado) y usarlo donde lo necesite.

Modificar la agregación

Otra opción es usar $replaceRoot de MongoDB para "reorganizar" los documentos de resultados, por lo que una estructura "simple" cubrirá perfectamente los documentos:

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

Con esta reasignación, los documentos de resultados se pueden modelar así:

type UserWithPosts struct {
    User  *User   `bson:"user"`
    Posts []*Post `bson:"posts"`
}

Tenga en cuenta que si bien esto funciona, las posts El campo de todos los documentos se obtendrá del servidor dos veces:una vez como posts campo de los documentos devueltos, y una vez como el campo de user; no lo mapeamos ni lo usamos, pero está presente en los documentos de resultados. Entonces, si se elige esta solución, el user.posts el campo debe eliminarse, p. con un $project etapa:

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