我的数据代表一个字典,它接收一堆更新和可能的新字段(元数据被添加到帖子中)。如下所示:
> db.collection.find()
{ _id: ..., 'A': 'apple', 'B': 'banana'},
{ _id: ..., 'A': 'artichoke'},
{ _id: ..., 'B': 'blueberry'},
{ _id: ..., 'C': 'cranberry'}
挑战 - 我想找到每个键忽略空白值的第一个(或最后一个)值(即我希望某种条件组可用于非文档级别的字段)。 (相当于更新后元数据的起始或结束版本。)
问题在于:
> db.collection.aggregate([{$group:
{_id: null, A: {$last: '$A'},
B: {$last: '$B'},
C: {$last: '$C'}
}
}])
用空值填充空格(而不是在结果中跳过它们),所以我得到了
{'_id': ..., 'A': null, 'B': null, 'C': 'cranberry'}
我想要的时候:
{'_id': ..., 'A': 'artichoke', 'B': 'blueberry', 'C': cranberry'}
答案 0 :(得分:1)
所以我刚考虑如何回答这个问题,但是有兴趣听听人们对这是对还是错的看法。基于@NeilLunn的回复,我想我会达到BSON限制,使他的版本更好地提取数据,但对我的应用程序来说,我可以一次运行这个查询。 (也许我真正的问题是数据设计)。
我们遇到的问题是,在“group by”中,我们为每个文档提供A,B,C版本。所以我的解决方案是通过改变(略微)原始数据结构告诉引擎它应该引入哪些字段,以告诉引擎每个文档中有哪些键:
> db.collection.find()
{ _id: ..., 'A': 'apple', 'B': 'banana', 'Keys': ['A', 'B']},
{ _id: ..., 'A': 'artichoke', 'Keys': ['A']},
{ _id: ..., 'B': 'blueberry', 'Keys': ['B']},
{ _id: ..., 'C': 'cranberry', 'Keys': ['C']}
现在,我们可以在$unwind
上'Keys'
,然后将'Keys'
与'_id'
分组。因此:
db.collection.aggregate([
{'$unwind': 'Keys'},
{'$group':
{'_id': 'Keys',
'A': {'$last': '$A'},
'B': {'$last': '$B'},
'C': {'$last': '$C'}
}
}
])
我找回了一系列_id
等于密钥的文档:
{_id: 'A', 'A': 'artichoke', 'B': null, 'C': null},
{_id: 'B', 'A': null, 'B': 'blueberry', 'C': null},
{_id: 'C', 'A': null, 'B': null, 'C': 'cranberry'}
然后,您可以提取所需的结果,因为知道密钥X
的值仅对_id为X
的结果有效。
(当然下一个问题是如何将这一系列文件减少到一个,每次都取适当的字段)
答案 1 :(得分:0)
我不认为这是你真正想要的,但它确实解决了你所问的问题。聚合框架不能真正做到这一点,因为你要求"最后的结果"来自不同文档的不同列。真的只有一种方法可以做到这一点,而且非常疯狂:
db.collection.aggregate([
{ "$group": {
"_id": null,
"A": { "$push": "$A" },
"B": { "$push": "$B" },
"C": { "$push": "$C" }
}},
{ "$unwind": "$A" },
{ "$group": {
"_id": null,
"A": { "$last": "$A" },
"B": { "$last": "$B" },
"C": { "$last": "$C" }
}},
{ "$unwind": "$B" },
{ "$group": {
"_id": null,
"A": { "$last": "$A" },
"B": { "$last": "$B" },
"C": { "$last": "$C" }
}},
{ "$unwind": "$C" },
{ "$group": {
"_id": null,
"A": { "$last": "$A" },
"B": { "$last": "$B" },
"C": { "$last": "$C" }
}},
])
基本上,您压缩文档将所有找到的元素推送到数组中。然后展开每个数组,并从那里取出$last
元素。您需要为每个字段执行此操作,以获取每个数组的最后一个元素,这是该字段的最后一个匹配。
并不是真正的好,并且肯定会在任何有意义的集合上爆炸BSON 16MB限制。
所以你真正追求的是最后一次见到"每个字段的值。您可以通过迭代集合并保留不是null
的值来强制执行此操作。您甚至可以使用mapReduce在服务器上执行此操作:
db.collection.mapReduce(
function () {
if (start == 0)
emit( 1, "A" );
start++;
current = this;
Object.keys(store).forEach(function(key) {
if ( current.hasOwnProperty(key) )
store[key] = current[key];
});
},
function(){},
{
"scope": { "start": 0, "store": { "A": null, "B": null, "C": null } },
"finalize": function(){ return store },
"out": { "inline": 1 }
}
)
这也可行,但迭代整个集合几乎与将所有内容混合在一起一样糟糕。
在这种情况下,您真正想要的是三个查询,理想情况是并行获取每个属性的最后一个谨慎值:
> db.collection.find({ "A": { "$exists": true } }).sort({ "$natural": -1 }).limit(1)
{ "_id" : ObjectId("54b319cd6997a054ce4d71e7"), "A" : "artichoke" }
> db.collection.find({ "B": { "$exists": true } }).sort({ "$natural": -1 }).limit(1)
{ "_id" : ObjectId("54b319cd6997a054ce4d71e8"), "B" : "blueberry" }
> db.collection.find({ "C": { "$exists": true } }).sort({ "$natural": -1 }).limit(1)
{ "_id" : ObjectId("54b319cd6997a054ce4d71e9"), "C" : "cranberry" }
更好的是通过$gt
和空字符串在每个属性和查询上创建稀疏索引。这确保使用索引,并且作为稀疏索引,它将仅包含属性所在的文档。您需要.hint()
这个,但您仍然需要排序$natural
:
db.collection.ensureIndex({ "A": -1 },{ "sparse": 1 })
db.collection.ensureIndex({ "B": -1 },{ "sparse": 1 })
db.collection.ensureIndex({ "C": -1 },{ "sparse": 1 })
> db.collection.find({ "A": { "$gt": "" } }).hint({ "A": -1 }).sort({ "$natural": -1 }).limit(1)
{ "_id" : ObjectId("54b319cd6997a054ce4d71e7"), "A" : "artichoke" }
> db.collection.find({ "B": { "$gt": "" } }).hint({ "B": -1 }).sort({ "$natural": -1 }).limit(1)
{ "_id" : ObjectId("54b319cd6997a054ce4d71e8"), "B" : "blueberry" }
> db.collection.find({ "C": { "$gt": "" } }).hint({ "C": -1 }).sort({ "$natural": -1 }).limit(1)
{ "_id" : ObjectId("54b319cd6997a054ce4d71e9"), "C" : "cranberry" }
这是解决你在这里所说内容的最好方法。但正如我所说,这就是你认为你需要解决它的方式。你真正的问题可能有另一种方法来处理存储和查询。
答案 2 :(得分:0)
对于使用Mongo 3.6
或$first
作为从分组记录中获取一个值(不一定是实际的第一个或最后一个)的方式的人,从$last
开始,$group
' s $mergeObjects
可以用作从分组项目中查找非空值的方法:
// { "A" : "apple", "B" : "banana" }
// { "A" : "artichoke" }
// { "B" : "blueberry" }
// { "C" : "cranberry" }
db.collection.aggregate([
{ $group: {
_id: null,
A: { $mergeObjects: { a: "$A" } },
B: { $mergeObjects: { b: "$B" } },
C: { $mergeObjects: { c: "$C" } }
}}
])
// { _id: null, A: { a: "artichoke" }, B: { b: "blueberry" }, C: { c: "cranberry" } }
$mergeObjects
根据每个分组的记录累积一个对象。需要注意的是$mergeObjects
将合并为不是null
的优先级值。但这需要修改对象的累积字段,从而修改“笨拙” { a: "$A" }
。
如果输出格式不完全符合您的期望,则可以始终使用附加的$project
阶段。