Найти все дубликаты документов в коллекции MongoDB по ключевому полю
Предположим, что у меня есть коллекция с некоторым набором документов. что-то вроде этого.
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":1, "name" : "foo"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":2, "name" : "bar"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":3, "name" : "baz"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":4, "name" : "foo"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":5, "name" : "bar"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":6, "name" : "bar"}
Я хочу найти все дублированные записи в этой коллекции по полю "имя". Например. "foo" появляется дважды, а "bar" появляется 3 раза.
Ответы
Ответ 1
Примечание: это решение проще всего понять, но не лучшее.
Вы можете использовать mapReduce
, чтобы узнать, сколько раз документ содержит определенное поле:
var map = function(){
if(this.name) {
emit(this.name, 1);
}
}
var reduce = function(key, values){
return Array.sum(values);
}
var res = db.collection.mapReduce(map, reduce, {out:{ inline : 1}});
db[res.result].find({value: {$gt: 1}}).sort({value: -1});
Ответ 2
Принятый ответ ужасно медленный в больших коллекциях и не возвращает _id
дубликатов записей.
Агрегация выполняется намного быстрее и может вернуть _id
s:
db.collection.aggregate([
{ $group: {
_id: { name: "$name" }, // replace `name` here twice
uniqueIds: { $addToSet: "$_id" },
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);
На первом этапе конвейера агрегации $group
оператор агрегирует документы в поле name
и сохраняет в uniqueIds
каждое значение _id
сгруппированных записей.
Оператор $sum добавляет значения полей, переданных ему, в этом случае константа 1
- тем самым подсчитывая количество сгруппированных записей в count
поле.
На втором этапе конвейера мы используем $match
для фильтрации документов с count
не менее 2, то есть дубликатов.
Затем мы сначала сортируем наиболее часто повторяющиеся дубликаты и ограничиваем результаты до лучших 10.
Этот запрос будет выводить до $limit
записей с повторяющимися именами вместе со своими _id
s. Например:
{
"_id" : {
"name" : "Toothpick"
},
"uniqueIds" : [
"xzuzJd2qatfJCSvkN",
"9bpewBsKbrGBQexv4",
"fi3Gscg9M64BQdArv",
],
"count" : 3
},
{
"_id" : {
"name" : "Broom"
},
"uniqueIds" : [
"3vwny3YEj2qBsmmhA",
"gJeWGcuX6Wk69oFYD"
],
"count" : 2
}
Ответ 3
Для общего решения Mongo см. рецепт Cookbook MongoDB для поиска дубликатов с помощью group
. Обратите внимание, что агрегация выполняется быстрее и мощнее, так как она может вернуть _id
дубликатов записей.
Для pymongo, принятый ответ (с использованием mapReduce) не так эффективен. Вместо этого мы можем использовать метод group:
$connection = 'mongodb://localhost:27017';
$con = new Mongo($connection); // mongo db connection
$db = $con->test; // database
$collection = $db->prb; // table
$keys = array("name" => 1); Select name field, group by it
// set intial values
$initial = array("count" => 0);
// JavaScript function to perform
$reduce = "function (obj, prev) { prev.count++; }";
$g = $collection->group($keys, $initial, $reduce);
echo "<pre>";
print_r($g);
Выход будет следующим:
Array
(
[retval] => Array
(
[0] => Array
(
[name] =>
[count] => 1
)
[1] => Array
(
[name] => MongoDB
[count] => 2
)
)
[count] => 3
[keys] => 2
[ok] => 1
)
Эквивалентный SQL-запрос будет выглядеть следующим образом: SELECT name, COUNT(name) FROM prb GROUP BY name
. Обратите внимание, что нам по-прежнему нужно отфильтровать элементы с числом 0 из массива. Опять же, обратитесь к рецепту поваренной книги MongoDB для поиска дубликатов с использованием group
для канонического решения с помощью group
.
Ответ 4
Я нашел полезную информацию о официальном блоге лаборатории mongo:
http://blog.mongolab.com/2014/03/finding-duplicate-keys-with-the-mongodb-aggregation-framework/