Наивысший принятый ответ здесь:
uniqueIds: { $addToSet: "$_id" },
Это также вернет вам новое поле с именем uniqueIds со списком идентификаторов. Но что, если вы просто хотите поле и его количество? Тогда было бы так:
db.collection.aggregate([
{$group: { _id: {name: "$name"},
count: {$sum: 1} } },
{$match: { count: {"$gt": 1} } }
]);
Чтобы объяснить это, если вы пришли из баз данных SQL, таких как MySQL и PostgreSQL, вы привыкли к агрегатным функциям (например, COUNT (), SUM (), MIN (), MAX ()), которые работают с оператором GROUP BY, что позволяет Вы, например, чтобы найти общее количество, что значение столбца появляется в таблице.
SELECT COUNT(*), my_type FROM table GROUP BY my_type;
+----------+-----------------+
| COUNT(*) | my_type |
+----------+-----------------+
| 3 | Contact |
| 1 | Practice |
| 1 | Prospect |
| 1 | Task |
+----------+-----------------+
Как вы можете видеть, наш вывод показывает количество, в котором появляется каждое значение my_type. Чтобы найти дубликаты в MongoDB, мы бы решили проблему аналогичным образом. MongoDB может похвастаться операциями агрегации, которые группируют значения из нескольких документов вместе, и могут выполнять различные операции над сгруппированными данными для возврата одного результата. Это похоже на агрегатные функции в SQL.
Предполагая, что коллекция называется контактами, первоначальная настройка выглядит следующим образом:
db.contacts.aggregate([ ... ]);
Эта агрегатная функция принимает массив операторов агрегирования, и в нашем случае нам нужен оператор $ group, поскольку наша цель - сгруппировать данные по количеству полей, то есть по числу вхождений значения поля.
db.contacts.aggregate([
{$group: {
_id: {name: "$name"}
}
}
]);
В этом подходе есть небольшая особенность. Поле _id необходимо для использования группы по оператору. В этом случае мы группируем поле $ name. Имя ключа в _id может иметь любое имя. Но мы используем имя, поскольку оно интуитивно понятно.
Запустив агрегацию, используя только оператор $ group, мы получим список всех полей имени (независимо от того, появляются ли они в коллекции один или несколько раз):
db.contacts.aggregate([
{$group: {
_id: {name: "$name"}
}
}
]);
{ "_id" : { "name" : "John" } }
{ "_id" : { "name" : "Joan" } }
{ "_id" : { "name" : "Stephen" } }
{ "_id" : { "name" : "Rod" } }
{ "_id" : { "name" : "Albert" } }
{ "_id" : { "name" : "Amanda" } }
Обратите внимание, как работает агрегация. Он взял документы с полями имени и возвращает новую коллекцию извлеченных полей имени.
Но мы хотим знать, сколько раз значение поля появляется снова. Оператор $ group берет поле count, которое использует оператор $ sum для добавления выражения 1 к итогу для каждого документа в группе. Таким образом, $ group и $ sum вместе возвращают общую сумму всех числовых значений, которые получаются для данного поля (например, name).
db.contacts.aggregate([
{$group: {
_id: {name: "$name"},
count: {$sum: 1}
}
}
]);
{ "_id" : { "name" : "John" }, "count" : 1 }
{ "_id" : { "name" : "Joan" }, "count" : 3 }
{ "_id" : { "name" : "Stephen" }, "count" : 2 }
{ "_id" : { "name" : "Rod" }, "count" : 3 }
{ "_id" : { "name" : "Albert" }, "count" : 2 }
{ "_id" : { "name" : "Amanda" }, "count" : 1 }
Поскольку целью было устранить дубликаты, требуется один дополнительный шаг. Чтобы получить только те группы, у которых число больше одного, мы можем использовать оператор $ match для фильтрации наших результатов. В операторе $ match мы скажем, чтобы он смотрел на поле count и велел искать счетчики больше единицы, используя оператор $ gt, представляющий «больше чем» и число 1.
db.contacts.aggregate([
{$group: { _id: {name: "$name"},
count: {$sum: 1} } },
{$match: { count: {"$gt": 1} } }
]);
{ "_id" : { "name" : "Joan" }, "count" : 3 }
{ "_id" : { "name" : "Stephen" }, "count" : 2 }
{ "_id" : { "name" : "Rod" }, "count" : 3 }
{ "_id" : { "name" : "Albert" }, "count" : 2 }
В качестве примечания: если вы используете MongoDB через ORM, например Mongoid для Ruby, вы можете получить эту ошибку:
The 'cursor' option is required, except for aggregate with the explain argument
Скорее всего, это означает, что ваш ORM устарел и выполняет операции, которые MongoDB больше не поддерживает. Следовательно, либо обновите свой ORM, либо найдите исправление. Для Mongoid это было исправлением для меня:
module Moped
class Collection
# Mongo 3.6 requires a `cursor` option be passed as part of aggregate queries. This overrides
# `Moped::Collection#aggregate` to include a cursor, which is not provided by Moped otherwise.
#
# Per the [MongoDB documentation](https://docs.mongodb.com/manual/reference/command/aggregate/):
#
# Changed in version 3.6: MongoDB 3.6 removes the use of `aggregate` command *without* the `cursor` option unless
# the command includes the `explain` option. Unless you include the `explain` option, you must specify the
# `cursor` option.
#
# To indicate a cursor with the default batch size, specify `cursor: {}`.
#
# To indicate a cursor with a non-default batch size, use `cursor: { batchSize: <num> }`.
#
def aggregate(*pipeline)
# Ordering of keys apparently matters to Mongo -- `aggregate` has to come before `cursor` here.
extract_result(session.command(aggregate: name, pipeline: pipeline.flatten, cursor: {}))
end
private
def extract_result(response)
response.key?("cursor") ? response["cursor"]["firstBatch"] : response["result"]
end
end
end