Go - mgo, получить все вложенные поля из коллекции

У меня есть структура БД, определенная следующим образом.

{
    name: "Jane",
    films: [
        {
            title: "The Shawshank Redemption",
            year: "1994"
        },
        {
            title: "The Godfather",
            year: "1972"
        }
    ]
},
{
    name: "Jack",
    films: [
        {
            title: "12 Angry Men",
            year: "1957"
        },
        {
            title: "The Dark Knight",
            year: "2008"
        }
    ]
}

Я хочу вернуть срез всех фильмов - []Film и, если возможно, в другом запросе срез всех титров - []string из коллекции. Я могу вытащить всю коллекцию и извлечь соответствующие данные в логике приложения, но возможно ли это сделать в рамках запроса? Я пытался работать с методом Select(), примерно так: c.Find(nil).Select(<various conditions>).All(&results) но у меня не получилось.


person curious_gudleif    schedule 04.08.2016    source источник


Ответы (1)


Я думаю, что это один из самых популярных вопросов по тегу mongo. И я должен сказать, если вам нужно, что вы делаете что-то не так, и, возможно, вам следует использовать СУБД вместо Mongo, поскольку падение производительности на такого рода запросах сведет на нет всю прибыль от функций Mongo, таких как документы без схемы, «все-в-одном» и т. д. ..

В любом случае, ответ прост - вы не можете получить список фильмов так, как хотите. Mongo find может возвращать только полные или частичные документы верхнего уровня. Я имею в виду, что лучший результат, который вы можете получить с помощью запроса db.collection.find({}, {'films': 1}), — это список, подобный

{
    films: [
        {
            title: "The Shawshank Redemption",
            year: 1994
        },
        {
            title: "The Godfather",
            year: 1972
        }
    ]
},
{
    films: [
        {
            title: "12 Angry Men",
            year: 1957
        },
        {
            title: "The Dark Knight",
            year: 2008
        }
    ]
}

Не то, что вы ожидали, верно?

Единственный способ получить массив, например

{
    title: "The Shawshank Redemption",
    year: 1994
},
{
    title: "The Godfather",
    year: 1972
},
{
    title: "12 Angry Men",
    year: 1957
},
{
    title: "The Dark Knight",
    year: 2008
}

заключается в использовании агрегации.

Основной запрос Mongo для получения массива фильмов:

db.collection.aggregate([{
    $unwind: '$films'
}, {
    $project: {
        title: '$films.title',
        year: '$films.year'
    }
}])

Код Go для этого запроса

package main

import (
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
    "fmt"
)

func main() {
    session, err := mgo.Dial("mongodb://127.0.0.1:27017/db")

    if err != nil {
        panic(err)
    }
    defer session.Close()
    session.SetMode(mgo.Monotonic, true)

    c := session.DB("db").C("collection")

    pipe := c.Pipe(
        []bson.M{
            bson.M{
                "$unwind": "$films",
            },
            bson.M{
                "$project": bson.M{
                    "title": "$films.title",
                    "year": "$films.year",
                },
            },
        },
    )
    result := []bson.M{}
    err = pipe.All(&result)
    fmt.Printf("%+v", result) // [map[_id:ObjectIdHex("57a2ed6640ce01187e1c9164") title:The Shawshank Redemption year:1994] map[_id:ObjectIdHex("57a2ed6640ce01187e1c9164") title:The Godfather year:1972] map[_id:ObjectIdHex("57a2ed6f40ce01187e1c9165") title:12 Angry Men year:1957] map[year:2008 _id:ObjectIdHex("57a2ed6f40ce01187e1c9165") title:The Dark Knight]]
}

Если вам нужны дополнительные условия для выбора документов верхнего уровня, код будет

pipe := c.Pipe(
    []bson.M{
        bson.M{
            "$match": bson.M{
                "name": "Jane",
            },
        },
        bson.M{
            "$unwind": "$films",
        },
        bson.M{
            "$project": bson.M{
                "title": "$films.title",
                "year": "$films.year",
            },
        },
    },
)
// result [map[_id:ObjectIdHex("57a2ed6640ce01187e1c9164") title:The Shawshank Redemption year:1994] map[title:The Godfather year:1972 _id:ObjectIdHex("57a2ed6640ce01187e1c9164")]]

И если вам нужно отфильтровать фильмы, вы можете использовать следующий запрос

pipe := c.Pipe(
    []bson.M{
        bson.M{
            "$unwind": "$films",
        },
        bson.M{
            "$project": bson.M{
                "title": "$films.title",
                "year": "$films.year",
            },
        },
        bson.M{
            "$match": bson.M{
                "year": bson.M{
                    "$gt": 2000,
                },
            },
        },
    },
)
// result [map[_id:ObjectIdHex("57a2ed6f40ce01187e1c9165") year:2008 title:The Dark Knight]]

Проблема с агрегацией заключается в том простом факте, что большая часть операций агрегации не использует индексы и может выполняться медленно на больших коллекциях. Вот почему я предложил вам подумать о СУБД, которая может быть лучшим выбором, если вам нужно много агрегаций.

И нет никакого способа получить []string из mgo, так как он всегда возвращает bson.M (или []bson.M), что равно map[string]interface{}.

person CrazyCrow    schedule 04.08.2016
comment
Большое спасибо за подробный ответ. Возможно, вы правы, и мне было бы лучше с Postgres. - person curious_gudleif; 04.08.2016
comment
@curious_gudleif умный выбор. - person CrazyCrow; 04.08.2016