Счетчик выбора MongoDB (отдельный x) в индексированном столбце - подсчет уникальных результатов для больших наборов данных

82

Я просмотрел несколько статей и примеров, но еще не нашел эффективного способа выполнить этот SQL-запрос в MongoDB (где есть миллионы ряды документы)

Первая попытка

(например, из этого почти повторяющегося вопроса - монго-эквивалент SQL SELECT DISTINCT? )

db.myCollection.distinct("myIndexedNonUniqueField").length

Очевидно, я получил эту ошибку, так как мой набор данных огромен

Thu Aug 02 12:55:24 uncaught exception: distinct failed: {
        "errmsg" : "exception: distinct too big, 16mb cap",
        "code" : 10044,
        "ok" : 0
}

Вторая попытка

Я решил попробовать сделать группу

db.myCollection.group({key: {myIndexedNonUniqueField: 1},
                initial: {count: 0}, 
                 reduce: function (obj, prev) { prev.count++;} } );

Но вместо этого я получил это сообщение об ошибке:

exception: group() can't handle more than 20000 unique keys

Третья попытка

Я еще не пробовал, но есть несколько предложений, касающихся mapReduce

например

Также

Кажется, есть запрос на перенос на GitHub, исправляющий .distinctметод, чтобы упомянуть, что он должен возвращать только счетчик, но он все еще открыт: https://github.com/mongodb/mongo/pull/34

Но в этот момент я подумал, что стоит спросить здесь, что нового по этой теме? Должен ли я перейти на SQL или другую базу данных NoSQL для различных подсчетов? или есть эффективный способ?

Обновить:

Этот комментарий к официальным документам MongoDB не обнадеживает, это точно?

http://www.mongodb.org/display/DOCS/Aggregation#comment-430445808

Обновление2:

Кажется, новая структура агрегирования отвечает на приведенный выше комментарий ... (MongoDB 2.1 / 2.2 и выше, доступна предварительная версия для разработки, не для производства)

http://docs.mongodb.org/manual/applications/aggregation/

Эран Медан
источник
Я предполагаю, что вам нужно делать это часто, иначе производительность не будет иметь большого значения. В этом случае я бы сохранил отдельные значения в отдельной коллекции, которая обновляется при вставке нового документа, вместо того, чтобы пытаться сделать отдельные значения в такой большой коллекции. Либо так, либо я пересмотрю свое использование MongoDb и, возможно, перейду к чему-то другому. Как вы обнаружили, MongoDb в настоящее время не подходит для того, что вы пытаетесь сделать.
Тим Готье
@TimGautier, спасибо, я так боялся, потребовались часы, чтобы вставить все эти значения, и я должен был подумать об этом раньше :) Думаю, сейчас я потрачу время, чтобы вставить его в MySQL для этой статистики ...
Эран Medan
Вы также можете выполнить инкрементный MR, в основном имитируя дельта-индексирование агрегированных данных. Я имею в виду, что то, что вы используете, зависит от того, когда вам нужны результаты. Я могу представить, что MySQL, вероятно, получит много операций ввода-вывода, а что нет (я могу убить небольшой сервер, выделив только 100 тыс. Документов, встроенных в индекс), но я полагаю, что он более гибок в запросах для такого рода вещей. .
Sammaye 02
Я не согласен с тем, что монго не очень хорош в подобных вещах. Это то, в чем Mongo преуспевает.
superluminary
1
К сожалению, модератор удалил мой ответ, который я также разместил на повторяющийся вопрос. Я не могу удалить его там и разместить здесь репост, таким образом, ссылка: stackoverflow.com/a/33418582/226895
эксперт

Ответы:

75

1) Самый простой способ сделать это - использовать фреймворк агрегации. Для этого нужны две команды «$ group»: первая группирует по отдельным значениям, вторая подсчитывает все отдельные значения.

pipeline = [ 
    { $group: { _id: "$myIndexedNonUniqueField"}  },
    { $group: { _id: 1, count: { $sum: 1 } } }
];

//
// Run the aggregation command
//
R = db.runCommand( 
    {
    "aggregate": "myCollection" , 
    "pipeline": pipeline
    }
);
printjson(R);

2) Если вы хотите сделать это с помощью Map / Reduce, вы можете. Это также двухэтапный процесс: на первом этапе мы создаем новую коллекцию со списком всех отдельных значений ключа. Во втором мы делаем count () для новой коллекции.

var SOURCE = db.myCollection;
var DEST = db.distinct
DEST.drop();


map = function() {
  emit( this.myIndexedNonUniqueField , {count: 1});
}

reduce = function(key, values) {
  var count = 0;

  values.forEach(function(v) {
    count += v['count'];        // count each distinct value for lagniappe
  });

  return {count: count};
};

//
// run map/reduce
//
res = SOURCE.mapReduce( map, reduce, 
    { out: 'distinct', 
     verbose: true
    }
    );

print( "distinct count= " + res.counts.output );
print( "distinct count=", DEST.count() );

Обратите внимание, что вы не можете вернуть результат встроенной карты / уменьшения, потому что это потенциально может превысить ограничение на размер документа 16 МБ. Вы можете сохранить вычисление в коллекции, а затем count () размер коллекции или вы можете получить количество результатов из возвращаемого значения mapReduce ().

Уильям Z
источник
5
Я загрузил Mongo 2.2 RC0 и использовал ваше первое предложение, и оно работает! и быстро! спасибо (молодец 10gen ...) Создал здесь суть (использовал сокращенную команду агрегирования и поместил ее в одну строку) gist.github.com/3241616
Эран Медан
@EranMedan Я должен вас предупредить, я не предлагал структуру агрегации, потому что 2.2 rc0 все еще не совсем готов для полного развертывания, просто о чем-то, о чем нужно помнить, я бы дождался полного выпуска 2.2, прежде чем рекомендовать развертывание агрегации фреймворк.
Sammaye 03
@Sammaye да, спасибо, я знаю об этом, пока не пойдет в производство, мне это нужно для внутренней статистики, и я хотел избежать переноса данных в SQL, если это возможно (и утолить мое любопытство)
Эран Медан
Почему Mongo не принимает: this.plugins.X-Powered-By.string? Как мне этого избежать?
EarlyPoster
Мне интересно, надежен ли этот ответ для сегментированной среды. Насколько я понимаю, каждый шард будет выполнять свою собственную агрегацию, а затем возвращать результат, который затем будет агрегирован. Итак, в этом сценарии, разве у нас не будет возможности для существования дубликатов, поскольку отдельные значения были потеряны во втором $groupоператоре перед передачей обратно в mongos?
Верран
37
db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}});

прямо к результату:

db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}})
   .result[0].count;
Stackee007
источник
1
Хорошо, так лучше. Но разве это не тот ответ, который уже дал Уильям?
JohnnyHK
2
Похоже, но мне нравится, что он на одной строке. Однако у меня возникла ошибка: «Не могу прочитать свойство« 0 »из неопределенного». Удалите последнюю строку, и она отлично работает.
Нико
и если мы говорим о действительно огромной базе данных, не забывайте {allowDiskUse: true}, так что db.myCollection.aggregate ([{$ group ..}, {$ group:}], {allowDiskUse: true}). result [ 0] .count;
hi_artem
3

Следующее решение сработало для меня

db.test.distinct ('пользователь'); [«Алекс», «Англия», «Франция», «Австралия»]

db.countries.distinct ('страна'). length 4

Муниб мир
источник