假设我有一些包含一些文档的集合。像这样的东西。
{ "_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"}
我想通过“name”字段找到此集合中的所有重复条目。例如。 “foo”出现两次,“bar”出现3次。
答案 0 :(得分:144)
大型集合上接受的答案非常慢,并且不会返回重复记录的_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
个。例如:
{
"_id" : {
"name" : "Toothpick"
},
"uniqueIds" : [
"xzuzJd2qatfJCSvkN",
"9bpewBsKbrGBQexv4",
"fi3Gscg9M64BQdArv",
],
"count" : 3
},
{
"_id" : {
"name" : "Broom"
},
"uniqueIds" : [
"3vwny3YEj2qBsmmhA",
"gJeWGcuX6Wk69oFYD"
],
"count" : 2
}
答案 1 :(得分:17)
注意:这个解决方案最容易理解,但不是最好的。
您可以使用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 :(得分:5)
对于通用的Mongo解决方案,请参阅MongoDB cookbook recipe for finding duplicates using 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的元素。同样,请使用group
参考规范解决方案的MongoDB cookbook recipe for finding duplicates using group
。
答案 3 :(得分:2)
我在官方mongo实验室博客上找到了有用的信息: http://blog.mongolab.com/2014/03/finding-duplicate-keys-with-the-mongodb-aggregation-framework/
答案 4 :(得分:0)
此处接受的最高答案是:
uniqueIds: { $addToSet: "$_id" },
这还将为您返回一个名为id的新字段,其中包含ID列表。但是,如果您只想要该字段及其数量怎么办?那么就是这样:
db.collection.aggregate([
{$group: { _id: {name: "$name"},
count: {$sum: 1} } },
{$match: { count: {"$gt": 1} } }
]);
为解释这一点,如果您来自MySQL和PostgreSQL等SQL数据库,则习惯于聚合函数(例如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运算符采用一个计数字段,该字段使用$ 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 }
由于目标是消除重复项,因此需要多执行一个步骤。要仅获取计数大于一个的组,可以使用$ match运算符过滤结果。在$ match运算符内,我们将告诉它查看count字段,并使用表示“大于”的$ gt运算符和数字1告诉它查找大于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