【问题标题】:MongoDB aggregate by field existsMongoDB 按字段聚合存在
【发布时间】:2014-10-19 06:24:33
【问题描述】:

我很难相信这个问题还没有在某个地方被问过和回答,但我找不到它的任何踪迹。

我有一个需要按布尔值分组的 MongoDB 聚合查询:是否存在另一个字段。

例如,让我们从这个集合开始:

> db.test.find()
{ "_id" : ObjectId("53fbede62827b89e4f86c12e"),
  "field" : ObjectId("53fbede62827b89e4f86c12d"), "name" : "Erik" }
{ "_id" : ObjectId("53fbee002827b89e4f86c12f"), "name" : "Erik" }
{ "_id" : ObjectId("53fbee092827b89e4f86c131"),
  "field" : ObjectId("53fbee092827b89e4f86c130"), "name" : "John" }
{ "_id" : ObjectId("53fbee122827b89e4f86c132"), "name" : "Ben" }

2 个文档有“字段”,2 个没有。 请注意,“字段”的每个值可能不同;我们只想对它的存在进行分组(或者非空值也适用于我,我没有存储任何空值)。

我尝试过使用 $project,但那里不存在 $exists,并且 $cond 和 $ifNull 没有帮助我。该字段似乎总是存在,即使它不存在:

> db.test.aggregate(
  {$project:{fieldExists:{$cond:[{$eq:["$field", null]}, false, true]}}},
  {$group:{_id:"$fieldExists", count:{$sum:1}}}
)
{ "_id" : true, "count" : 4 }

我希望以下更简单的聚合可以工作,但由于某种原因 $exists 不支持这种方式:

> db.test.aggregate({$group:{_id:{$exists:"$field"}, count:{$sum:1}}})
assert: command failed: {
  "errmsg" : "exception: invalid operator '$exists'",
  "code" : 15999,
  "ok" : 0
} : aggregate failed
Error: command failed: {
  "errmsg" : "exception: invalid operator '$exists'",
  "code" : 15999,
  "ok" : 0
} : aggregate failed
    at Error (<anonymous>)
    at doassert (src/mongo/shell/assert.js:11:14)
    at Function.assert.commandWorked (src/mongo/shell/assert.js:244:5)
    at DBCollection.aggregate (src/mongo/shell/collection.js:1149:12)
    at (shell):1:9
2014-08-25T19:19:42.344-0700 Error: command failed: {
  "errmsg" : "exception: invalid operator '$exists'",
  "code" : 15999,
  "ok" : 0
} : aggregate failed at src/mongo/shell/assert.js:13

有谁知道如何从这样的集合中获得所需的结果?

预期结果:

{ "_id" : true, "count" : 2 }
{ "_id" : false, "count" : 2 }

【问题讨论】:

    标签: mongodb aggregation-framework


    【解决方案1】:

    $exists 运算符是一个“查询”运算符,因此它主要用于“过滤”结果而不是识别逻辑条件。

    作为“逻辑”运算符,聚合框架支持$ifNull 运算符。这将返回它存在的字段值或它不存在或以其他方式计算为null的备用提供值@

    db.test.aggregate([
        { "$group": {
            "_id": { "$ifNull": [ "$field", false ] },
            "count": { "$sum": 1 }
        }}
    ])
    

    当然,即使这不是“真/假”比较,所以除非你真的想返回它所在字段的实际值,那么你最好使用$cond 语句像你一样:

    db.test.aggregate([
        { "$group": {
            "_id": { "$cond": [{ "$eq": [ "$field", null ] }, true, false ] },
            "count": { "$sum": 1 }
        }}
    ])
    

    $ifNull 在替换不存在的数组字段时非常有用,否则使用 $unwind 会导致错误。然后,您可以执行诸如返回单个元素或空数组之类的操作,这样就不会在管道处理的其余部分中造成问题。

    【讨论】:

    • 正如我在 OP 中指出的那样,您的解决方案给出了错误的结果:{ "_id" : false, "count" : 4 } 感谢您的回答。
    • 实际上,$eq 始终返回 false,即使该字段存在。但如果使用 $gt 它可以工作。使用 "_id": { "$cond": [{ "$gt": [ "$field", null ] }, true, false ] } 代替
    • $eq 不检查字段是否存在。谢谢@RomanBlachman 的 $gt 小费
    • 使用$match有区别吗?因为我刚刚尝试了$match: { var_name: { $exists : true } } 并且聚合工作正常。
    【解决方案2】:

    我昨晚解决了同样的问题,这样:

    > db.test.aggregate({$group:{_id:{$gt:["$field", null]}, count:{$sum:1}}})
    { "_id" : true, "count" : 2 }
    { "_id" : false, "count" : 2 }
    

    请参阅 http://docs.mongodb.org/manual/reference/bson-types/#bson-types-comparison-order 了解其工作原理的完整说明。

    【讨论】:

    • 不错,聪明的解决方案!我没有意识到 BSON 定义了不同类型的比较。
    • 这当然很聪明,我也没有更好的解决方案,但感觉就像一个 hack。
    • 要检查该值是否不存在或为空,请使用{ $lte: ["$field", null] }
    • @RickyBoyce 感谢您的提示。我正在使用 $eq 并且无法弄清楚为什么我没有得到预期的结果。切换到 $lte 现在一切正常。
    • 我不知道为什么没有提到这一点,但对我来说$match: { var_name: { $exists : true } } 工作得很好。我还没有测试过这个来对聚合进行分组。
    【解决方案3】:

    我通过检查未定义解决了它

    $ne : [$var_to_check, undefined]
    

    $ne:  [ { $type : "$var_to_check"}, 'missing'] }
    

    如果定义了 var,则返回 true

    【讨论】:

    【解决方案4】:

    不知道情况如何,但现在 2019 年有了干净的解决方案。在聚合管道中这样做

    $match: {"my_field": {$ne: null}}
    

    在我的语言中,“ne”的意思是不是 :)

    【讨论】:

    • 我认为可能不适用于聚合管道,最终使用$gt
    • 上述查询将返回 my_field 不存在的文档。这不是预期的。
    • ne 表示not equal
    • 我们是说“ne”的骑士!
    【解决方案5】:

    总之

    {'$project': {
        'field_exists': {'$or': [
            {'$eq': ['$field', null]}, 
            {'$gt': ['$field', null]},
        ]},
    }}
    

    详情

    $exists 表示该字段存在,即使它是null 或任何其他空值。这就是为什么这个页面上的所有答案都是错误的。

    让我们测试一下。检查这个:

    // Let's take any collection that have docs
    db.getCollection('collection').aggregate([
      // Get arbitrary doc, no matter which, we won't use it
      {"$limit": 1},
      // Project our own fields (just create them with $literal)
      {'$project': {
        '_id': 0,
        'null_field': {'$literal': null},
        'not_null_field': {'$literal': {}},
      }},
    ])
    

    我们会得到这个:

    {
        "null_field" : null,
        "not_null_field" : {}
    }
    

    那么让我们澄清一下这个文档中存在哪些字段:

    1. null_field - 存在
    2. not_null_field - 存在
    3. non_existent_field - 没有。

    好的,是时候测试我上面提到的项目阶段了。让我们为我们感兴趣的每个字段添加它:

    {'$project': {
        'null_field_exists': {'$or': [
            {'$eq': ['$null_field', null]}, 
            {'$gt': ['$null_field', null]},
        ]},
        'not_null_field_exists': {'$or': [
            {'$eq': ['$not_null_field', null]}, 
            {'$gt': ['$not_null_field', null]},
        ]},
        'non_existent_field_exists': {'$or': [
            {'$eq': ['$non_existent_field', null]}, 
            {'$gt': ['$non_existent_field', null]},
        ]},
    }},
    

    我们得到的是:

    {
        "null_field_exists" : true,
        "not_null_field_exists" : true,
        "non_existent_field_exists" : false
    }
    

    正确!

    还有一个小提示:我们使用null 进行比较,因为它是至少有价值的最小值(较小的只是不存在)。

    【讨论】:

      【解决方案6】:

      我的回答是:

      {$match:{
          $and:[{
              name:{
                  $exists:true
              }
          }, {
              $expr:{
                  $eq:["$$id", "$_id"]
              }
          }]
      }}
      

      我在查找中使用它,在我的管道阶段。 这个帖子2规则第一个,名字必须存在。第二件事是这两个集合之间的关系。 我相信你可以为你的问题修改这个。

      【讨论】:

        【解决方案7】:

        在猫鼬中只有以下工作

        $ne:  [ { $type : "$var_to_check"}, 'missing'] }
        

        【讨论】:

          【解决方案8】:

          我使用 $addFields$ifNull 解决了这个问题,然后通过检查其值是否为空来$match 添加的字段。

          collection.aggregate(
              [
                 {
                    $addFields:{
                        fieldName:{
                           $ifNull:["$fieldToCheckIfExists", null]
                        }
                    }
                 },
                 {
                    $match:{
                        fieldName:{
                          $ne: null
                    }
                 }
              ]
          

          【讨论】:

            【解决方案9】:

            检查字段是否存在且不为空的语义透明解决方案:

            { $ne: [{ $ifNull: ["$field", null] }, null] }
            

            要检查它是否丢失,请将$ne 替换为$eq

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-12-02
              • 1970-01-01
              • 2023-01-04
              • 2013-03-19
              • 2020-11-09
              相关资源
              最近更新 更多