这里接受的最高答案是:
uniqueIds: { $addToSet: "$_id" },
这也将返回给您一个名为 uniqueIds 的新字段,其中包含 id 列表。但是,如果您只想要该字段及其计数怎么办?那么它会是这样的:
db.collection.aggregate([
{$group: { _id: {name: "$name"},
count: {$sum: 1} } },
{$match: { count: {"$gt": 1} } }
]);
为了解释这一点,如果您来自 MySQL 和 PostgreSQL 等 SQL 数据库,您习惯于使用与 GROUP BY 语句一起使用的聚合函数(例如 COUNT()、SUM()、MIN()、MAX()),例如例如,查找列值出现在表中的总计数。
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 中聚合函数的概念。
假设有一个名为 contacts 的集合,初始设置如下所示:
db.contacts.aggregate([ ... ]);
这个聚合函数接受一个聚合运算符数组,在我们的例子中,我们需要 $group 运算符,因为我们的目标是按字段的计数对数据进行分组,即字段值的出现次数。
db.contacts.aggregate([
{$group: {
_id: {name: "$name"}
}
}
]);
这种方法有一点点怪癖。_id 字段是使用 group by 运算符所必需的。在这种情况下,我们对 $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 运算符采用一个计数字段,该字段使用 $sum 运算符将表达式 1 添加到组中每个文档的总数中。因此,$group 和 $sum 一起返回给定字段(例如名称)产生的所有数值的总和。
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 }
由于目标是消除重复,它需要一个额外的步骤。要仅获取计数大于 1 的组,我们可以使用 $match 运算符来过滤我们的结果。在 $match 运算符中,我们将告诉它查看计数字段并告诉它使用表示“大于”和数字 1 的 $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 }
附带说明一下,如果您通过像 Mongoid for Ruby 这样的 ORM 使用 MongoDB,您可能会收到以下错误:
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