如何遍历命名键并获取结果

时间:2018-11-19 01:27:25

标签: mongodb mongodb-query aggregation-framework

我正在查询mongo db中的嵌套Json,示例数据结构如下所示:

{
    "_id" : ObjectId("5bf159cc6bf6ab0ac374f80c"),
    "name" : "Jack",
    "age" : "30",
    "info" : {
        "0" : {
            "status" : "true",
            "name" : "luffy"
        },
        "1" : {
            "status" : "true",
            "name" : "sanji"
        },
        "2" : {
            "status" : "false",
            "name" : "zoro"
        }
    }
}

/* 2 */
{
    "_id" : ObjectId("5bf15f286bf6ab0ac374f8ed"),
    "name" : "Mack",
    "age" : "33",
    "info" : {
        "0" : {
            "status" : "true",
            "name" : "naruto"
        },
        "1" : {
            "status" : "true",
            "name" : "sakura"
        },
        "2" : {
            "status" : "false",
            "name" : "sasuke"

现在我想做的就是查询并获取status ='true'的那些结果。经过一番谷歌搜索之后,我开始知道如何查询嵌套文档并提出了一个示例。

query:db.getCollection('test').find({"info.0.status":"true"})

但是从上面的查询中可以知道,该查询只会从'0th'数组中获取适当的结果。如何获取查询以遍历数组并返回带有“ status”:“ true”的文档。我也是Mongodb的新手,请忽略任何错误。

注意:其中一位用户告诉我,我应该如下所示重新构建我的数据结构,然后使用$ filter运算符:

[
  {
    "_id": ObjectId("5bf159cc6bf6ab0ac374f80c"),
    "name": "Jack",
    "age": "30",
    "info": [
      {
        "status": "true",
        "name": "luffy"
      },
      {
        "status": "true",
        "name": "sanji"
      },
      {
        "status": "false",
        "name": "zoro"
      }
    ]
  },
  {
    "_id": ObjectId("5bf15f286bf6ab0ac374f8ed"),
    "name": "Mack",
    "age": "33",
    "info": [
      {
        "status": "true",
        "name": "naruto"
      },
      {
        "status": "true",
        "name": "sakura"
      },
      {
        "status": "false",
        "name": "sasuke"
      }
    ]
  }
]

但是我没有得到如何按照用户显示的方式来重塑我的结构。还有其他方法可以使用吗?

1 个答案:

答案 0 :(得分:1)

一般问题的底线是不要尝试以这种形式“查询”,而是接受建议并重写数据。但是,有不同的记录方法。

重写集合

正如您已经被告知的那样,最好对集合进行重新建模,使它们成为真实的“数组”,而不是带有命名键的“对象”。

一般情况下,实际上是“迭代”收集项并重写它们:

var updates = [];
db.getCollection('test').find().forEach(doc => {
  var info = Object.keys(doc.info).map(k => 
    Object.assign({}, doc.info[k], { status: doc.info[k].status === "true" }) );

  updates.push({
    "updateOne": {
      "filter": { "_id": doc._id },
      "update": { "$set": { "doc.info": info } }
    }
  });

  if (updates.length >= 1000) {
    db.getCollection('test').bulkWrite(updates);
    updates = [];
  }

});

if (updates.length >= 0) {
  db.getCollection('test').bulkWrite(updates);
  updates = [];
}

或者编写一个全新的集合:

db.getCollection('test').aggregate([
  { "$addFields": {
    "info": {
      "$map": {
        "input": { "$objectToArray": "$info" },
        "in": {
          "$mergeObjects": [
            "$$this.v",
            { "status": { "$toBool": "$$this.v.status" }
          ]
        }
      }
    }
  }},
  { "$out": "newtest" }
])

基本的依赖就是您是否可以容忍“新集合”,并且实际上拥有MongoDB版本可用的$objectToArray之类的功能。

即使在“更新”时,通常也建议(尤其是在生产中)改用How to Update Multiple Array Elements in mongodb,因为使用$set代替整个属性是“蛮力”,而不是安全生产。很好,尽管仅在您自己的系统上进行测试。

完成其中任何一种表格后,您基本上可以$filter仅进行true的匹配,如:

collection.aggregate([
  // Finds valid "documents" 
  { "$match": { "info.status": true } },
  // "filters" the array content
  { "$addFields": {
    "info": {
      "$filter": { "input": "$info", "cond": "$$this.status" }
    }
  }}
])

就地查询

您当然可以使用当前文档结构实际进行查询,但这只是 不推荐

collection.aggregate([
  // Match on transformed object
  { "$match": {
    "$expr": {
      "$gt": [
        { "$size": {
          "$filter": {
            "input": {
              "$map": {
                "input": { "$objectToArray": "$info" },
                "in": "$$this.v"
              }
            },
            "cond": { "$toBool": "$$this.status" }
          }
        }},
        0
      ]
    }
  }},
  // Transform remaining objects
  { "$addFields": {
    "info": {
      "$filter": {
        "input": {
          "$map": {
            "input": { "$objectToArray": "$info" },
            "in": "$$this.v"
          }
        },
        "cond": { "$toBool": "$$this.status" }
      }
    }
  }}
])

或者甚至在$where中使用JavaScript表达式,当然也不支持在从服务器检索之前实际“过滤”结果内容:

collection.find({
  "$where": function() {
    return Object.keys(this.info).map( k => this.info[k])
      .some(e => e.status === "true")
  }
})

使用JavaScript在服务器上更改文档的唯一内容是mapReduce,它当然具有其自身的特定格式:

collection.mapReduce(
  function() {
    var id = this._id;
    delete this._id;
    this.info = Object.keys(this.info)
      .map(k => this.info[k])
      .filter(o => o.status === "true")
    emit(id,this);
  },
  function() {},
  {
    "out": { "inline": 1 },
    "query": {
      "$where": function() {
        return Object.keys(this.info).map( k => this.info[k])
          .some(e => e.status === "true")
      }
    }
  }
)

在这两种情况下,它们都是“可怕的” ,因为它们基本上依赖于在应用之前可以将每个文档转换为实际的“数组”形式。另一方面,重写集合实际上允许事先完成该工作,因此删除了这样的 computation ,还允许指定"index"以便加快实际查询结果的速度。


简而言之,重写,并且不要“查询”它的当前状态,因为在跨文档查询时,数据库并未真正针对“命名键”进行优化。