2013-07-20 3 views
12

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

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

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

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

Я что-то пропустил? Есть идеи? Благодаря!

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

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

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

+0

Было бы полезно, если бы вы добавили свои запросы в качестве примера к вопросу –

+0

Добавлен образец кода. Вопрос, однако, является общим (я думаю). Благодаря! –

ответ

3

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

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

+0

Спасибо за ответ. Полезно знать. Пошел на комбинацию решений в моем реальном приложении, например, используя, по возможности, $ match, предварительно вычисляя, где это возможно, и просто делайте без учета в других случаях. Вышеприведенный запрос был просто примером (поскольку меня попросили предоставить код). –

+3

@ Дилан, знаете ли вы, были ли эти улучшения еще закончены? – Dan

0

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

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

пример:

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); 
}); 
0

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

0

У меня такая же проблема, и решить с $ проекта, $ ломтика и $$ 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/

2

Существует решение, используя толчок и срез: https://stackoverflow.com/a/39784851/4752635 (@emaniacs упоминает его здесь, а).

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

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

Я поселил с этим решением, используя 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 
      }); 
      } 
     }); 
    } 
    }); 
}); 
Смежные вопросы