MongoDB选择索引列上的count(distinct x)-计算大型数据集的唯一结果


82

我已经阅读了几篇文章和示例,还没有找到在MongoDB中进行此SQL查询的有效方法(那里有数百万个 行数 文件)

第一次尝试

(例如,来自这个几乎重复的问题-Mongo是否等同于SQL的SELECT DISTINCT?

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上有一个pull请求,修复了.distinct提及该方法只应返回一个计数的方法,但它仍处于打开状态:https : //github.com/mongodb/mongo/pull/34

但是在这一点上,我认为值得在这里问一下,关于该主题的最新信息是什么?我应该转移到SQL或另一个NoSQL DB以获得不同的计数吗?还是有一种有效的方法?

更新:

对MongoDB官方文档的评论并不令人鼓舞,这是正确的吗?

http://www.mongodb.org/display/DOCS/Aggregation#comment-430445808

更新2:

似乎新的Aggregation Framework回答了上述评论...(MongoDB 2.1 / 2.2及更高版本,提供开发预览,不适用于生产)

http://docs.mongodb.org/manual/applications/aggregation/


我认为您需要经常执行此操作,否则性能不会有太大关系。在那种情况下,我会将不同的值存储在一个单独的集合中,当您插入一个新文档时,该集合会更新,而不是尝试对那么大的集合进行不同的处理。要么,要么我重新评估对MongoDb的使用,然后可能会转移到其他地方。如您所见,MongoDb目前不擅长于您要尝试做的事情。
2012年

@TimGautier谢谢,我担心如此,插入所有这些值需要花费几个小时,以前我应该已经想到了:)我想我现在将花一些时间将其插入MySQL以获取这些统计信息……
Eran棉兰2012年

您也可以执行增量MR,基本上模拟聚集数据的增量索引。我的意思是,这取决于何时需要使用结果。我可以想象MySQL会大量获取IO,而这样做不会有什么好处(我可以杀死一个小型服务器,只在索引上内嵌10万个文档),但是我认为它在查询这类内容时更加灵活。
Sammaye

我不同意mongo在这种事情上不擅长。这种事情是Mongo擅长的。
superluminary

1
不幸的是,主持人删除了我在重复问题中也发布的答案。我无法将其删除并在此处重新发布,因此链接:stackoverflow.com/a/33418582/226895
Expert

Answers:


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做到这一点,则可以。这也是一个分为两个阶段的过程:在第一阶段,我们将构建一个新集合,其中包含键的每个不同值的列表。在第二个中,我们对新集合执行count()。

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() );

请注意,您无法返回内联映射/缩小的结果,因为这可能会超出16MB的文档大小限制。您可以将计算结果保存在集合中,然后对集合的大小进行count()运算,也可以从mapReduce()的返回值中获取结果数。


5
我下载了Mongo 2.2 RC0,并使用了您的第一个建议,它可行!又快!谢谢(做得好10gen ...)在这里创建了一个要点(使用快捷方式聚合命令并将其放在一行中)gist.github.com/3241616
Eran Medan

@EranMedan但我应该警告您,我不建议使用聚合框架,因为2.2 rc0尚未真正为完全部署做好准备,只是要记住一点,我会等到2.2的完整发行版之后再建议部署聚合框架。
Sammaye

@Sammaye是的,谢谢(我已经知道了),将不会投入生产,我需要它来进行内部统计,并希望避免在可能的情况下将数据移至SQL(并保持好奇心)
Eran Medan 2012年

Mongo为什么不接受:this.plugins.X-Powered-By.string?我将如何逃脱?
EarlyPoster 2012年

我想知道此答案对于分片环境是否可靠。据我了解,碎片将各自进行自己的汇总,然后返回结果,然后汇总结果。因此,在这种情况下,由于不同的值$group在传递回mongos之前已在第二条语句中丢失了,因此我们是否有机会存在重复项?
Verran 2015年

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

2
类似,但是我喜欢它在一行上的事实。但是,我得到一个错误:“无法读取未定义的属性'0'”删除最后一行,它可以正常工作。
Nico

如果我们谈论的是真正的大型数据库,请不要忘记{allowDiskUse:true},所以db.myCollection.aggregate([{$ group ..},{$ group:}],{allowDiskUse:true} .result [ 0] .count;
hi_artem

3

以下解决方案为我工作

db.test.distinct('user'); [“ alex”,“ England”,“ France”,“ Australia”]

db.countries.distinct('country')。length 4

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.