Подтвердить что ты не робот

MongoDB - Структура агрегации (общее количество)

При запуске обычного запроса "найти" на MongoDB я могу получить итоговое количество результатов (независимо от ограничения), запустив "count" на возвращаемом курсоре. Таким образом, даже если я ограничу результатом набора до 10 (например), я все еще могу знать, что общее число результатов было 53 (опять же, например).

Если я понимаю это правильно, структура агрегации, однако, не возвращает курсор, а просто результаты. Итак, если я использовал оператор конвейера $limit, как я могу узнать общее количество результатов независимо от указанного предела?

Думаю, я мог бы запустить агрегацию дважды (один раз, чтобы подсчитать результаты через $group и один раз с $limit для фактических ограниченных результатов), но это кажется неэффективным.

Альтернативный подход может заключаться в том, чтобы связать общее количество результатов с документами (через $group) до операции $limit, но это также кажется неэффективным, поскольку этот номер будет прикреплен к каждому документу (а не просто возвращается один раз для набора).

Я что-то упустил? Есть идеи? Спасибо!

Например, если это запрос:

db.article.aggregate(
    { $group : {
        _id : "$author",
        posts : { $sum : 1 }
    }},
    { $sort : { posts: -1 } },
    { $limit : 5 }
);

Как узнать, сколько результатов доступно (до $limit)? Результат не является курсором, поэтому я не могу просто рассчитывать на него.

4b9b3361

Ответ 1

Assaf, в ближайшем будущем будут некоторые улучшения в структуре агрегации, которые могут позволить вам делать ваши вычисления за один проход легко, но сейчас лучше всего выполнять ваши вычисления, параллельно используя два запроса: один для агрегирования #posts для ваших лучших авторов и другого агрегата для расчета общих постов для всех авторов. Также обратите внимание, что если все, что вам нужно сделать, это подсчет документов, использование функции подсчета является очень эффективным способом выполнения расчета. Тайны MongoDB учитываются в индексах btree, что позволяет очень быстро рассчитывать на запросы.

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

Ответ 2

Существует решение с использованием push и slice: fooobar.com/questions/181954/... (здесь также упоминается @emaniacs).

Но я предпочитаю использовать 2 запроса. Решение с нажатием $$ ROOT и использованием $slice работает с ограничением памяти документа в 16 МБ для больших коллекций. Кроме того, для больших коллекций два запроса вместе работают быстрее, чем один с нажатием $$ ROOT. Вы можете запускать их параллельно, поэтому вы ограничены только медленными двумя запросами (возможно, теми, которые сортируются).

  • Сначала для фильтрации, а затем группировки по идентификатору, чтобы получить количество фильтрованных элементов. Не фильтруйте здесь, это не нужно.
  • Второй запрос, который фильтрует, сортирует и разбивает страницы.

Я решил использовать это решение, используя 2 запроса и структуру агрегации (примечание - я использую node.js в этом примере):

var aggregation = [
  {
    // If you can match fields at the begining, match as many as early as possible.
    $match: {...}
  },
  {
    // Projection.
    $project: {...}
  },
  {
    // Some things you can match only after projection or grouping, so do it now.
    $match: {...}
  }
];


// Copy filtering elements from the pipeline - this is the same for both counting number of fileter elements and for pagination queries.
var aggregationPaginated = aggregation.slice(0);

// Count filtered elements.
aggregation.push(
  {
    $group: {
      _id: null,
      count: { $sum: 1 }
    }
  }
);

// Sort in pagination query.
aggregationPaginated.push(
  {
    $sort: sorting
  }
);

// Paginate.
aggregationPaginated.push(
  {
    $limit: skip + length
  },
  {
    $skip: skip
  }
);

// I use mongoose.

// Get total count.
model.count(function(errCount, totalCount) {
  // Count filtered.
  model.aggregate(aggregation)
  .allowDiskUse(true)
  .exec(
  function(errFind, documents) {
    if (errFind) {
      // Errors.
      res.status(503);
      return res.json({
        'success': false,
        'response': 'err_counting'
      });
    }
    else {
      // Number of filtered elements.
      var numFiltered = documents[0].count;

      // Filter, sort and pagiante.
      model.request.aggregate(aggregationPaginated)
      .allowDiskUse(true)
      .exec(
        function(errFindP, documentsP) {
          if (errFindP) {
            // Errors.
            res.status(503);
            return res.json({
              'success': false,
              'response': 'err_pagination'
            });
          }
          else {
            return res.json({
              'success': true,
              'recordsTotal': totalCount,
              'recordsFiltered': numFiltered,
              'response': documentsP
            });
          }
      });
    }
  });
});

Ответ 3

Если вы не хотите запускать два запроса параллельно (один для агрегирования #posts для ваших лучших авторов и другой агрегации для расчета общих постов для всех авторов), вы можете просто удалить $limit для конвейера и результатов вы можете использовать

totalCount = results.length;
results.slice(number of skip,number of skip + number of limit);

ex:

db.article.aggregate([
    { $group : {
        _id : "$author",
        posts : { $sum : 1 }
    }},
    { $sort : { posts: -1 } }
    //{$skip : yourSkip},    //--remove this
    //{ $limit : yourLimit }, // remove this too
]).exec(function(err, results){
  var totalCount = results.length;//--GEt total count here
   results.slice(yourSkip,yourSkip+yourLimit);
});

Ответ 4

в моем случае мы используем $out stage, чтобы скомпилировать набор результатов из агрегирования в таблицу temp/cache, а затем подсчитать его. и, поскольку нам нужно сортировать и разбивать на страницы результаты, мы добавляем индекс в таблицу temp и сохраняем имя таблицы в сеансе, удаляем таблицу при закрытии сессии/тайм-ауте кэша.

Ответ 5

У меня такая же проблема, и я решил с помощью $project, $slice и $$ ROOT.

db.article.aggregate(
{ $group : {
    _id : '$author',
    posts : { $sum : 1 },
    articles: {$push: '$$ROOT'},
}},
{ $sort : { posts: -1 } },
{ $project: {total: '$posts', articles: {$slice: ['$articles', from, to]}},
).toArray(function(err, result){
    var articles = result[0].articles;
    var total = result[0].total;
});

Вам нужно объявить переменную from и to.

https://docs.mongodb.com/manual/reference/operator/aggregation/slice/

Ответ 6

Я получаю общее количество с aggregate().toArray().length