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


82

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

Перша спроба

(наприклад, із цього майже дубльованого запитання - монгоський еквівалент вибору SELECT DISTINCT в SQL? )

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 для цієї статистики ...
Еран Медан

Ви також можете зробити додатковий MR, в основному емулюючи дельта-індексацію сукупних даних. Я маю на увазі, що це залежить від того, коли вам потрібні результати щодо того, що ви використовуєте. Я можу уявити, що MySQL отримає багато введення-виводу, а що не від цього (я можу вбити невеликий сервер, виділяючи в індексі лише 100 тис. Документів, вбудованих в індекс), але я вважаю, що він є більш гнучким у запитах для такого роду речей, як і раніше .
Sammaye

Я не згоден з тим, що монго не вміє робити такі речі. Подібне, якщо щось є тим, чим Монго перевершує.
надсвітлий

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, можете. Це також двофазний процес: на першому етапі ми створюємо нову колекцію зі списком усіх окремих значень ключа. У другому ми робимо підрахунок () нової колекції.

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 МБ. Ви можете зберегти обчислення в колекції, а потім підрахувати () розмір колекції, або ви можете отримати кількість результатів із поверненого значення mapReduce ().


5
Я завантажив Mongo 2.2 RC0 і використав вашу першу пропозицію, і це працює! і швидко! дякую (молодець 10gen ...) Створив тут суть (використовував комбіновану
Еран Медан,

@EranMedan, я хотів би попередити вас, однак, я не пропонував структуру агрегування, оскільки 2.2 rc0 все ще не готовий до повного розгортання, просто щось на увазі, я зачекаю до повного випуску 2.2, перш ніж рекомендувати розгортання агрегації рамки.
Sammaye

@Sammaye так, дякую, я це знаю, поки що не піду на виробництво, мені це потрібно було для внутрішньої статистики і хотів уникнути переміщення даних у SQL, якщо це можливо (і вгамувати мою цікавість)
Еран Медан

Чому Монго не приймає: 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;

1
Правильно, це вже краще. Але хіба це не та сама відповідь, яку вже дав Вільям?
JohnnyHK 04.03.13

2
Подібне, але мені подобається той факт, що він на одному рядку. Однак я отримав помилку: "Не вдається прочитати властивість '0' з невизначеного" Видаліть останній рядок, і він працює чудово.
Ніко

і якщо ми говоримо про справді величезну базу даних, не забувайте {allowDiskUse: true}, отже, db.myCollection.aggregate ([{$ group ..}, {$ group:}], {allowDiskUse: true}). 0] .count;
hi_artem

3

Наступне рішення спрацювало для мене

db.test.distinct ('користувач'); ["Алекс", "Англія", "Франція", "Австралія"]

db.countries.distinct ('країна'). довжина 4

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.