在mongodb中与first或last进行聚合时,如何仅选择非null值?

问题描述 投票:2回答:3

我的数据表示一个字典,该字典接收大量更新和可能的新字段(将元数据添加到帖子中)。所以像:

> 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' }
  }}
])

用null填充空格(而不是在结果中跳过它们,所以我得到:

{ '_id': ..., 'A': null, 'B': null, 'C': 'cranberry' }

当我想要时:

{ '_id': ..., 'A': 'artichoke', 'B': 'blueberry', 'C': cranberry' }
mongodb mapreduce mongodb-query aggregation-framework
3个回答
1
投票

我不认为这不是您真正想要的,但是确实可以解决您所要求的问题。聚合框架实际上无法做到这一点,因为您要从不同的文档中获取不同列的“最后结果”。实际上只有一种方法可以做到,而且非常疯狂:

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限制。

所以您真正要寻找的是每个字段的“最后看到的”值。您可以通过迭代集合并保留非$last的值来强行执行此操作。您甚至可以使用mapReduce在这样的服务器上执行此操作:

null

这也可以,但是迭代整个集合几乎就像将所有内容与聚合混在一起一样糟糕。

在这种情况下,您真正​​想要的是三个查询,理想情况下是并行进行,以获取每个属性最后一次看到的谨慎值:

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,但仍需要$gt排序:

.hint()

这是解决您在这里所说的最好的方法。但是正如我所说,这就是您认为需要解决的方式。您真正的问题可能还有另一种方式来处理存储和查询。


0
投票

因此,我只是想过如何回答这个问题,但是希望听到人们对这是对/错有何看法。根据@NeilLunn的回复,我想我将达到BSON限制,使他的版本更适合拉取数据,但是对我的应用程序而言,一次可以运行此查询非常重要。 (也许我真正的问题是数据设计)。

我们遇到的问题是,在“分组依据”中,我们为每个文档引入了A,B,C版本。因此,我的解决方案是通过(略微)更改原始数据结构以告诉引擎每个引擎在每个文档中包含哪些键,从而告诉聚合应将哪些字段引入:

.hint()

现在我们可以在$natural上使用$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" } 分组为> 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'的值仅对_id为db.collection.aggregate([ {'$unwind': 'Keys'}, {'$group': {'_id': 'Keys', 'A': {'$last': '$A'}, 'B': {'$last': '$B'}, 'C': {'$last': '$C'} } } ]) 的结果有效。


0
投票
[开始_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作为从分组记录中获取一个值的方法(不一定是实际的第一个或最后一个),可以将XMongo 3.6用作从分组项目中查找非空值的方法:

$first

$last根据每个分组的记录累积一个对象。需要注意的是$group将合并为非$mergeObjects的优先级值。但这需要将累积的字段修改为对象,因此需要“笨拙” $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" } } 阶段。

© www.soinside.com 2019 - 2024. All rights reserved.