Статьи

Загадка комментариев

Один из самых распространенных вопросов, которые я вижу о разработке схемы MongoDB:

У меня есть коллекция постов в блоге, и у каждого поста есть множество комментариев. Как мне получить …
… все комментарии данного автора
… самые последние комментарии
… самые популярные комментаторы?

И так далее. Ответом на это всегда было «Ну, вы не можете сделать это на стороне сервера…». Вы можете сделать это на стороне клиента или сохранить комментарии в своей собственной коллекции. Что вам действительно нужно, так это умение рассматривать встроенные документы как «настоящую» коллекцию.

Конвейер агрегации дает вам эту возможность, позволяя вам «раскручивать» массивы в отдельные документы, а затем делать все, что вам нужно сделать в последующих операторах конвейера.

Например…

Примечание. Для использования конвейера агрегации вы должны использовать как минимум версию 2.1.0 MongoDB.

Получение всех комментариев от Serious Cat

Комментарии Serious Cat разбросаны между почтовыми документами, поэтому не было хорошего способа запрашивать только эти встроенные документы. Сейчас есть.

Давайте предположим, что мы хотим, чтобы каждый комментарий Serious Cat, а также заголовок и URL-адрес поста, который комментировал Serious Cat. Итак, шаги, которые нам нужно предпринять:

  1. Извлеките нужные нам поля (заголовок, URL, комментарии)
  2. Разверните поле комментариев: превратите каждый комментарий в «настоящий» документ
  3. Запросите нашу новую «коллекцию комментариев» для «Serious Cat»

Используя конвейер агрегации, это выглядит так:

> db.runCommand({aggregate: "posts", pipeline: [
{
   // extract the fields 
   $project: {
        title : 1,
        url : 1,
        comments : 1
    }
},
{
    // explode the "comments" array into separate documents
    $unwind: "$comments"
},
{
    // query like a boss
    $match: {comments.author : "Serious Cat"}
}]})

Теперь это хорошо работает для чего-то вроде блога, где у вас есть созданные человеком данные (небольшие). Если у вас есть комментарии к комментариям, вы, вероятно, захотите отфильтровать как можно больше (например, с помощью $ match или $ limit), прежде чем отправлять их в части конвейера «все в памяти».

Получение самых последних комментариев

Давайте предположим, что на нашем сайте перечислены 10 самых последних комментариев по всем сообщениям со ссылками на сообщения, на которых они появились, например:

  1. Отличный пост! Джерри (2 февраля 2012 г.)
  2. Что значит батрахофаг? Фред (2 февраля 2012 г.) из фильма «Веселье с кроссвордами»
  3. Где я могу получить скидку на обувь Prada? -Tom (1 февраля 2012) из ​​Rant о спаме

Чтобы извлечь эти комментарии из коллекции сообщений, вы можете сделать что-то вроде:

> db.runCommand({aggregate: "posts", pipeline: [
{
   // extract the fields
   $project: {
        title : 1,
        url : 1,
        comments : 1
    }
{
    // explode "comments" array into separate documents
    $unwind: "$comments"
},
{
    // sort newest first
    $sort: {
        "comments.date" : -1
    }
},
{
    // get the 10 newest
    $limit: 10
}]})

Давайте на минутку посмотрим, что $ unwind делает с образцом документа.

Предположим, у вас есть документ, который выглядит следующим образом после $ project:

{
    "url" : "/blog/spam",
    "title" : "Rant about Spam",
    "comments" : [
        {text : "Where can I get discount Prada shoes?", ...},
        {text : "First!", ...},
        {text : "I hate spam, too!", ...},
        {text : "I love spam.", ...}
    ]
}

Затем, после раскручивания поля комментариев, вы получите:

{
    "url" : "/blog/spam",
    "title" : "Rant about Spam",
    "comments" : [
        {text : "Where can I get discount Prada shoes?", ...},
    ]
}
{
    "url" : "/blog/spam",
    "title" : "Rant about Spam",
    "comments" : [
        {text : "First!", ...}
    ]
}
{
    "url" : "/blog/spam",
    "title" : "Rant about Spam",
    "comments" : [
        {text : "I hate spam, too!", ...}
    ]
},
{
    "url" : "/blog/spam",
    "title" : "Rant about Spam",
    "comments" : [
        {text : "I love spam.", ...}
    ]
}

тогда мы $ sort, $ limit, а Боб твой дядя.

Рейтинг комментаторов по популярности

Предположим, мы разрешаем пользователям оставлять комментарии и хотим узнать, кто является наиболее популярным комментатором.

Шаги, которые мы хотим предпринять:

  1. Спроецируйте поля, которые нам нужны (аналогично приведенному выше)
  2. Развернуть массив комментариев (аналогично приведенному выше)
  3. Группировка по автору, подсчет голосов (это суммирует все голоса за каждый комментарий)
  4. Сортировка авторов, чтобы найти самые популярные комментаторы

Используя конвейер, это будет выглядеть так:

> db.runCommand({aggregate: "posts", pipeline: [
{
   // extract the fields we'll need
   $project: {
        title : 1,
        url : 1,
        comments : 1
    }
},
{
    // explode "comments" array into separate documents
    $unwind: "$comments"
},
{
    // count up votes by author
    $group : {
        _id : "$comments.author",
        popularity : {$sum : "$comments.votes"}
    }
},
{
    // sort by the new popular field
    $sort: {
        "popularity" : -1
    }
}]})

Как я упоминал ранее, есть несколько недостатков в использовании конвейера агрегации: большая часть конвейера выполняется в оперативной памяти и может потреблять очень много ресурсов процессора и памяти. Тем не менее, если использовать их разумно, это дает вам гораздо больше свободы для просмотра встроенных документов.