我有以下结构的文件:
{
...,
trials:[ {...,
ref:[{a:1,b:2},{a:2,b:2},...]
},
{...,
ref:[{a:1,b:2}]
},
...,
]
}
其中ref
是一个保证长度至少为1的数组。
如果我想计算每个ref
数组中每个元素的出现次数,我将使用以下聚合。 (这很好用)
db.cl.aggregate([
{$unwind:"$trials"},
{$unwind:"$trials.ref"},
{$group:{_id:"$trials.ref", count:{$sum:1}}}
])
现在我想做同样的事情,但只能使用每个ref
数组中的最后一个元素。我需要一种方法来只选择聚合管道中每个数组的最后一个元素。
我首先想到我可以通过这样的方式添加一个中间步骤来获取我想要分组的所有元素:
db.cl.aggregate([
{$unwind:"$trials"},
{$group:{_id:null,arr:{$push:"$trials.ref.-1"}}},...
])
我还尝试使用$match
的位置运算符。
db.cl.aggregate([
{$unwind:"$trials"},
{$match:{"trials.ref.$":-1}},...
])
或尝试投射最后一个元素。
db.cl.aggregate([
{$unwind:"$trials"},
{$project:{ref:"$trials.ref.1"}}
])
这些都没有让我到任何地方。 $pop
运算符在聚合管道中无效。 $last
运算符在这里并不实用。
关于如何仅使用ref
数组的最后一个元素的任何想法?我宁愿继续使用聚合框架,也不要使用Map Reduce。
答案 0 :(得分:1)
聚合框架实际上无法解决这个问题。除了缺少任何“切片”类型的运算符之外,这里真正的问题是缺少任何标记来判断内部数组的结束位置,并且实际上没有任何方法可以使用任何其他形式的文档重新整形。< / p>
至少现在,mapReduce方法非常简单,甚至不需要reducer:
db.cl.mapReduce(
function() {
this.trials.forEach(function(trial) {
trial.ref = trial.ref.slice(-1);
});
var id = this._id;
delete this._id;
emit( id, this );
},
function(){},
{ "out": { "inline": 1 } }
)
将来可能会有一些希望。某种形式的 $slice
已经寻求了一段时间。但我确实在$map
operator code内注意到了这个有趣的片段。只是在这里列出:
output.reserve(input.size());
for (size_t i=0; i < input.size(); i++) {
vars->setValue(_varId, input[i]);
Value toInsert = _each->evaluateInternal(vars);
if (toInsert.missing())
toInsert = Value(BSONNULL); // can't insert missing values into array
output.push_back(toInsert);
}
请注意for
循环和索引值。我会投票将这个暴露为$map
运算符中的变量,因为您知道当前位置和数组的长度,您可以有效地“切片”。
但是现在,没有办法告诉你使用$map
在数组中的位置,如果你$unwind
两个数组,你就会失去内部数组的端点。因此,现在的解决方案中缺乏聚合框架。