我退回了以下表格的文件:
{ _id: '48nmqsyxmswpkkded2ac_331fabf34fcd3935',
actions: { sales: { pixel: [Object] } },
date: Sun Jul 27 2014 00:00:00 GMT-0400 (EDT),
client: '48nmqsyxmswpkkded2ac',
campaignId: null,
domain: null,
affiliate: '964540',
subaffiliate: '11776-hlc-a-click-here' }
我想按客户分组,并且有四个字段用于显示在campaignId,domain,affiliate和subaffiliate中的唯一商品数量。理想情况下,我希望以下列形式返回文件:
{ client: '48nmqsyxmswpkkded2ac',
affiliates: 45,
subaffiliates: 51,
campaignIds: 2,
domains: 234 }
我正在尝试以下内容:
[ {
$match:
{
date: { $gte: dutil.getDateOnly(self.date), $lt: dutil.getDateOnly(new Date()) }
}
},
{
$group:
{
_id: "$client",
affiliate: { $addToSet : "$affiliate" },
//subaffiliate: { $addToSet : "$subaffiliate" }
},
},
],
{
allowDiskUse: true,
cursor: { batchSize: 40000 }
}
我可以在affiate.length之后获取唯一元素,但是当我取消关联子网络线时,此代码不起作用(没有文档返回),因为返回的66MB BSON对象大于Mongo的16MB限制。
@Neil Lunn这是现在的问题。我尝试将光标限制更改为40或10但错误相同。甚至返回光标并使用每个抛出相同的错误。我认为allowdiskuse选项会让mongo写入磁盘,因此没有16MB的限制。我错了吗?我现在的一些想法是
这两种想法似乎都是次优和杂乱的。有什么建议? Mongo意味着巨大;我被16MB的限制所困扰并不具有讽刺意味:)
答案 0 :(得分:2)
如果你真的在这里吹嘘BSON限制,让我们清楚你正在爆炸的是在你为每个分组文档创建的“集合”中,那么你真正拥有的数量远远超过你在预期成绩。至少在某些文件中是这样的。
真正的问题是,由于BSON限制,$addToSet
不会为你削减它。现在要克服的一个大问题是,你真的没有另外一种方法可以在一次传递中累积所有字段数。特别是考虑到你需要将累积的文件保持在BSON限制之下。
底线表示单独的查询。然后基本上“聚合”那些结果。单独查询的意思是“有效大小”形式,它将获得每个不同字段值的计数。基本上就是这样:
[
{ "$group": {
"_id": {
"client": "$client",
"afilliate": "$affiliate"
}
}},
{ "$group": {
"_id": "$_id.client",
"affiliates": { "$sum": 1 },
"subaffiliates": { "$sum": 0 },
"domains": { "$sum": 0 },
"campaignids": { "$sum": 0 }
}}
]
基本前提是返回特定字段的“计数”,主要是通过使用该字段作为“键”的一部分在初始$group
中查找“不同”项。第二个分组获取每个键下现在不同的术语的计数。完整的符号似乎是人为的,但它确实消除了在以后的处理中测试“空”或不存在字段的需要。
再次捕获这种方法,是你需要“组合”所有查询的结果,以确定最终结果。如图所示的查询表单不可能违反文档的BSON限制。所以现在唯一真正的问题是以某种方式“合并”每个查询的结果并以有效的方式进行。
此处的实际方法取决于您的“结果文档数量”需要“光标”迭代的位置,或者可以在单个结果中处理,其中每个响应都在16MB BSON限制之下。
这里的两种方法是:
使用返回的游标,并且通常在服务器上创建另一个集合以发出进一步的聚合语句。
如果生成的“集合”低于16MB BSON限制,那么您可以查看结果在内存中的聚合,这可能是最快的解决方案,这是一个选项。
一般情况归结为像这样处理“光标”:
var async = require('async'),
pluralize = require('pluralize'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var sampleSchema = new Schema({
date: Date,
client: String,
campaignId: Schema.Types.Mixed,
domain: Schema.Types.Mixed,
affiliate: String,
subaffiliate: String
});
var targetSchema = new Schema({},{ strict: false });
var Sample = mongoose.model( 'Sample', sampleSchema, 'sample' );
var Target = mongoose.model( 'Target', targetSchema, 'target' );
function getFields() {
return Object.keys( Sample.schema.paths ).filter(function(field) {
var blacklist = ['_id','__v','date','client'];
return blacklist.indexOf(field) == -1;
})
}
function getPipe(field,fields) {
var grp = { "client": "$client" };
grp[field] = "$" + field;
var pipe = [{ "$group": { "_id": grp }}];
var obj = { "_id": "$_id.client" };
fields.forEach(function(current) {
var plural = pluralize( current );
obj[plural] = {
"$sum": (field == current) ? 1 : 0
};
});
pipe.push({ "$group": obj });
return pipe;
}
var fields = getFields();
var tasks = {};
fields.forEach(function(current) {
var pipe = getPipe( current, fields );
tasks[current] = function(callback) {
var cursor = Sample.collection.aggregate(
pipe,
{ cursor: { batchSize: 100 } }
);
var bulk = Target.collection.initializeOrderedBulkOp();
var counter = 0;
cursor.on("data",function(item) {
var client = item._id;
delete item._id;
item.client = client;
console.log(item);
bulk.insert(item);
counter++;
if ( counter % 1000 == 0 )
bulk.execute(function(err,result) {
if (err) throw err;
bulk = Target.collection.initializeOrderdBulkOp();
});
});
cursor.on("end",function() {
if ( counter % 1000 != 0 )
bulk.execute(function(err,result) {
if (err) throw err;
callback(null,counter);
});
});
};
});
mongoose.connection.on("open",function(err,conn) {
async.parallel(
tasks,
function( err, results ) {
if (err) throw err;
console.log( results );
var obj = { "_id": "$client" };
fields.forEach(function(field) {
var plural = pluralize( field );
obj[plural] = { "$sum": "$" + plural };
});
var pipe = [{ "$group": obj }];
var cursor = Target.collection.aggregate(
pipe,
{ cursor: { batchSize: 100 } }
);
// do something with the cursor, like pipe or other event process
}
);
});
或者如果结果允许那么在内存中:
var sampleSchema = new Schema({
date: Date,
client: String,
campaignId: Schema.Types.Mixed,
domain: Schema.Types.Mixed,
affiliate: String,
subaffiliate: String
});
var Sample = mongoose.model( 'Sample', sampleSchema, 'sample' );
function getFields() {
return Object.keys( Sample.schema.paths ).filter(function(field) {
var blacklist = ['_id','__v','date','client'];
return blacklist.indexOf(field) == -1;
})
}
function getPipe(field,fields) {
var grp = { "client": "$client" };
grp[field] = "$" + field;
var pipe = [{ "$group": { "_id": grp }}];
var obj = { "_id": "$_id.client" };
fields.forEach(function(current) {
var plural = pluralize( current );
obj[plural] = {
"$sum": (field == current) ? 1 : 0
};
});
pipe.push({ "$group": obj });
return pipe;
}
var fields = getFields();
var tasks = {};
fields.forEach(function(current) {
var pipe = getPipe( current, fields );
tasks[current] = function(callback) {
Sample.collection.aggregate(
pipe,
function(err,result) {
callback(err,result);
}
);
};
});
mongoose.connection.on("open",function(err,conn) {
async.waterfall(
[
function(callback) {
async.parallel(
tasks,
function( err, results ) {
callback(err,results);
}
);
},
function(results,callback) {
async.concat(
fields,
function(item,callback) {
callback(null,results[item]);
},
function(err,results) {
callback(err,results);
}
);
},
function(results,callback) {
var obj = {};
results.forEach(function(item) {
if ( !obj.hasOwnProperty(item._id) ) {
var blank = {};
fields.map(function(field) {
return pluralize(field);
}).forEach(function(field) {
blank[field] = 0;
});
obj[item._id] = blank;
} else {
fields.map(function(field) {
return pluralize(field);
}).forEach(function(field) {
obj[item._id][field] += item[field];
});
}
});
callback(null,obj);
},
function(results,callback) {
var results = Object.keys( results ).map(function(id) {
var obj = { _id: id };
fields.map(function(field) {
return pluralize( field );
}).forEach(function(field) {
obj[field] = results[id][field];
});
return obj;
});
callback(null,results);
}
],
function(err,results) {
if (err) throw err;
console.log( results );
}
);
});
在两者中都有一些mongoose库的使用,至少为被引用的集合定义“模式”。因此,为了确定要使用的字段的“列表”,有一点内省,但您可以使用标准数组来执行此操作。
每个案例都会将"async.parallel"视为一种统一执行该服务器上的语句的方式,否则会“结合”结果。它实际上取决于结果集实际有多大,关于通过游标甚至$out
规范处理结果是否更有效,然后使用类似.eval()
之类的内容来保留所有内容那里作为服务器端操作。不是最好的选择,因为“.eval()”有自己的问题,你应该从提供的链接中读到。
无论如何,结合结果的并行处理方法是我能想到的唯一真正的选择:
$addToSet
创建了太大的文档,甚至可以使用$size
运算符进行缩减。所以问题已经发生了。
使分组更精细,首先说“单日”不会产生正确的“不同”计数。而不是在尝试任何导致与1相同问题的事情之前。
您不能“有条件地评估”跨文档的不同值。所有聚合操作一次只能处理一个文档,除非您故意将文档组合在一起,在这种情况下再次回到问题1.
您不能尝试一次对所有键进行分组,因为再次对多个字段的值进行分组会导致错误的不同计数,因为它们在某些组合中只是“不同”而不是单个“客户端”键
尝试使用内存选项,只要完整响应低于16MB,这将是最快的处理。