我有以下结构的文档:
{
...,
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。
聚合框架确实没有办法处理这个问题。除了缺乏任何"切片"类型操作符之外,这里的真正问题是缺乏任何标记来告诉您的内部数组在哪里结束,并且对于任何其他形式的文档重塑都没有任何方法可以做到这一点。
至少现在,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
操作符代码中有一个有趣的代码片段。只是在这里列出:
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
你的两个数组,你失去了内部数组的端点。因此,聚合框架在目前的解决方案中是缺乏的。