mgo/mongodb: агрегат — найти все и упорядочить по количеству участников, однако количество участников — это массив идентификаторов пользователей

Представление 1 записи (сообщество):

{
    "_id" : ObjectId("538a4734d6194c0e98000001"),
    "name" : "Darko",
    "description" : "Darko",
    "subdomain" : "darko",
    "domain" : "forum.dev",
    "created" : ISODate("2014-05-31T21:18:44.764Z"),
    "category" : "Art and Culture",
    "owner" : "53887456d6194c0f5b000001",
    "members" : [ 
        "53887456d6194c0f5b000001"
    ]
}

и тип Go

Community struct {
    Id          bson.ObjectId `bson:"_id,omitempty" json:"id"`
    Name        string        `json:"name"`
    Description string        `bson:",omitempty" json:"description"`
    Subdomain   string        `bson:",omitempty" json:"subdomain"`
    Domain      string        `json:"domain"`
    Created     time.Time     `json:"created"`
    Category    string        `json:"category"`
    Owner       string        `json:"owner"`
    Banned      []string      `bson:",omitempty" json:"banned"`
    Members     []string      `json:"members"`
    Moderators  []string      `bson:",omitempty" json:"moderators"`
    Admins      []string      `bson:",omitempty" json:"admins"`
    Logo        string        `bson:",omitempty" json:"logo"`
    Stylesheets []string      `bson:",omitempty" json:"stylesheets"`
    Javascripts []string      `bson:",omitempty" json:"javascripts"`
}

Хорошо, теперь я хотел бы получить список всех сообществ Category Art and Culture и упорядочить по количеству участников, также известных как members.length в js или len(Community.Members) в Go.

что-то вроде SELECT * FROM communities ORDER BY COUNT(members) WHERE category = 'Art and Culture'

У меня есть пользовательский тип, который нужно заполнить или рассортировать.

CommunityDirectory struct {
    Id          bson.ObjectId `bson:"_id,omitempty" json:"id"`
    Name        string        `json:"name"`
    Description string        `bson:",omitempty" json:"description"`
    Subdomain   string        `bson:",omitempty" json:"subdomain"`
    Domain      string        `json:"domain"`
    Created     time.Time     `json:"created"`
    Category    string        `json:"category"`
    Logo        string        `bson:",omitempty" json:"logo"`
    Membercount int64         `bson:"membercount" json:"membercount"`
}

Что у меня есть до сих пор

func (ctx *CommunityContext) Directory() {
    pipe := ccommunity.Pipe([]bson.M{bson.M{"membercount": bson.M{"$size": "members"}}})
    iter := pipe.Iter()
    result := CommunityDirectory{}
    results := []CommunityDirectory{}
    for {
        if iter.Next(&result) {
            results = append(results, result)
            fmt.Println(result)
        } else {
            break
        }
    }
    ctx.JSON(results)
}

но это не работает, потому что

db.communities.aggregate(
[
{"membercount": {$size:"members"}}
]
)

Error("Printing Stack Trace")@:0
()@src/mongo/shell/utils.js:37
([object Array])@src/mongo/shell/collection.js:866
@(shell):3

uncaught exception: aggregate failed: {
    "errmsg" : "exception: Unrecognized pipeline stage name: 'membercount'",
    "code" : 16436,
    "ok" : 0
}

Таким образом, он должен найти все, упорядочить по количеству участников и назначить новое «виртуальное» поле «Количество участников», но только категории «Искусство и культура».

Я нахожу MongoDB довольно сложной в этом отношении.

  1. Как выглядит запрос mongodb?

  2. Как это выглядит в Go/mgo?


person Community    schedule 01.06.2014    source источник


Ответы (1)


Есть несколько концепций, к которым нужно привыкнуть, когда вы новичок в платформе агрегации.

Правильная форма конвейера, как он будет работать в оболочке, должна быть такой:

db.communties.aggregate([

    // Match the documents first to filter and possibly make use of an index
    { "$match": {
        "category": "Art and Culture"
    }},

    // You include all fields when adding another and you want all
    { "$project": {
        "name": 1,
        "description": 1,
        "subdomain": 1,
        "domain": 1,
        "created": 1,
        "category": 1,
        "owner": 1,
        "members": 1,
        "memberCount": { "$size": "$members" }
    }},

    // $sort means "ORDER BY" in this case the ascending
    { "$sort": { "memberCount": 1 } },

    // Optionally project just the fields you need in the result
    { "$project": {
        "name": 1,
        "description": 1,
        "subdomain": 1,
        "domain": 1,
        "created": 1,
        "category": 1,
        "owner": 1,
        "members": 1
    }}
])

Таким образом, на самом деле нет прямого эквивалента «SELECT *», если вы вообще не хотите изменять структуру. Здесь вам нужно добавить поле «memberCount», поэтому вам нужно указать все поля. Вы можете использовать $$ROOT, который копирует все поля в документе, но вам нужно будет назначить это другому полю/свойству в вашем $project, например:

{ "$project": {
    "_id": "$$ROOT",
    "memberCount": 1
 }}

Но теперь, конечно, все ваши «поля» не совсем такие, какими они были, и все они имеют префикс _id.. Но это дело личного вкуса.

Следующее, к чему нужно привыкнуть, это всегда пытаться использовать $match первый. Это не только помогает сократить количество обрабатываемых документов по сравнению с остальной частью конвейера агрегации, но и является единственным шансом использовать индекс для оптимизации запроса. Как только вы измените документы на других этапах, использование индекса закончится, поскольку это больше не исходный источник, который был проиндексирован. На самом деле это не сильно отличается от SQL, но семантика несколько отличается от того, как вы указываете. Помните, что "конвейер" так же, как оператор "конвейер" | в Unix, поэтому сначала выполните "сопоставление".

Сортировка имеет свой собственный этап конвейера. Поэтому используйте оператор $sort для этапа конвейера, чтобы сделать это .

Окончательный $project является необязательным. Здесь мы просто отбрасываем поле «memberCount», которое использовалось для «сортировки».


Использование с mGo должно выглядеть так:

pipeline := [].bson.D{
    bson.M{"$match": bson.M{ "category": "Art and Culture" } },

    bson.M{"$project": bson.M{
        "name": 1,
        "description": 1,
        "subdomain": 1,
        "domain": 1,
        "created": 1,
        "category": 1,
        "owner": 1,
        "members": 1,
        "memberCount": bson.M{ "$size": "$members" }
    }},

    bson.M{ "$sort": bson.M{ "memberCount": 1 } },

    bson.M{ "$project": bson.M{
        "name": 1,
        "description": 1,
        "subdomain": 1,
        "domain": 1,
        "created": 1,
        "category": 1,
        "owner": 1,
        "members": 1
    }}
}

pipe := ccommunity.Pipe( pipeline )

Так что на самом деле это не так уж отличается от формы большинства примеров, которые вы найдете.

Возможно, просмотрите диаграмму сопоставления SQL и агрегации, представленную в основной документации, для других примеров распространенных Запросы SQL по мере их применения к конвейеру агрегации.

person Neil Lunn    schedule 02.06.2014
comment
Спасибо. Комментарий к трубке очень помог мне, когда я представлял себе, что там происходит. grep|awk|сортировка|awk. Таким образом, каждая строка передает результаты следующей строке, и это всегда {command: {condition/s}}. Очень хороший ответ, еще раз спасибо. - person ; 02.06.2014