【问题标题】:Mongo Query Nested Field Values with two-level unknown parent keysMongo 查询具有两级未知父键的嵌套字段值
【发布时间】:2018-01-16 10:58:35
【问题描述】:

我们使用 MongoDB 存储时间序列传感器数据,类似于 https://www.mongodb.com/blog/post/schema-design-for-time-series-data-in-mongodb 中设计的架构

我们在各个时间段内的数据查询方面确实取得了良好的性能。 关于我们的架构设计的说明: "v" 是传感器读数的父键,时间用分和秒转换成嵌套数组。我们使用 "m"(Minute) 作为子父键,然后使用 "s"(Second) 作为分钟读的子键。传感器读数位于 "s" 级别,其中 field1field2、...、field10 为传感器数据值。

现在我们正在尝试实现一些数据分析工具,并希望通过传感器数据读取值来查询数据。有没有一种有效的方法来查询数据而不在查询中使用嵌套的 for 循环?

例如:

  1. 具有传感器读数的项目:"field1">2
  2. 具有传感器读数的项目:"field1">2"field3">5

谢谢一百万。

记录类似于以下示例。

{
   "_id": ObjectId("5a5dd49f74bbaefd1ac89fc8"),
   "c_id": "1017",
   "c_id_s": NumberInt(1017),
   "c_t": NumberInt(1516096800),
   "type": "hour",
   "v": {
     "m1": {
       "s54": {
         "field1": 7.373158,
         "entry_id": NumberInt(4635),
         "field3": 0.19,
         "field2": NumberInt(88) 
      } 
    },
     "m31": {
       "s54": {
         "field1": 5.981918,
         "entry_id": NumberInt(4637),
         "field3": 0.04 
      },
       "s55": {
         "field2": NumberInt(89),
         "entry_id": NumberInt(4639),
         "field5": NumberInt(-67) 
      } 
    } 
  },
   "entry_id": NumberInt(4639) 
}, 
{
   "_id": ObjectId("5a5dd1a174bbaefd1ac89fc1"),
   "c_id": "1024",
   "c_id_s": NumberInt(1024),
   "c_t": NumberInt(1516096800),
   "type": "hour",
   "v": {
     "m3": {
       "s22": {
         "field3": 210.479996,
         "entry_id": NumberInt(30297) 
      },
       "s23": {
         "field1": 3.271534,
         "entry_id": NumberInt(30300),
         "field8": 7.1875,
         "field2": NumberInt(94) 
      } 
    },
     "m8": {
       "s23": {
         "field3": 150.639999,
         "entry_id": NumberInt(30304),
         "field1": 2.948425,
         "field8": 7.125,
         "field2": NumberInt(94) 
      } 
    },
     "m13": {
       "s23": {
         "field3": 99.799995,
         "entry_id": NumberInt(30308),
         "field1": 2.849621,
         "field8": 7.0625,
         "field2": NumberInt(95) 
      } 
    },
     "m18": {
       "s23": {
         "field3": 59.099998,
         "entry_id": NumberInt(30312),
         "field1": 2.681393,
         "field8": 6.9375,
         "field2": NumberInt(95) 
      } 
    },
     "m19": {
       "s8": {
         "field5": NumberInt(-87),
         "entry_id": NumberInt(30313) 
      } 
    } 
  },
   "entry_id": NumberInt(30313) 
}

【问题讨论】:

  • 请举例说明您希望返回的文档是什么样子。
  • 返回的文档可以是符合条件的全部文档
  • @dnickless 返回的文档可以是符合条件的整个文档
  • 目前我正在使用这种方式,但是速度很慢: {$and:[{c_id: "415"},{ $where: function() { for(var key in this.v) { for (var subKey in this.v[key]) { if(this.v[key][subKey].field1>2) { return true; } } } 返回错误; } } ] }
  • @leon 你找到什么了吗?我面临着类似的问题。

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

Map reduce 允许您处理命名键,但聚合是实现高效查询的方法。

您必须将数据建模为聚合框架的嵌入式文档数组。

我为您提供了两种选择。您可以针对您的数据集测试它们,看看哪一个更适合您。

类似

"v":[
  {
    "minute":1,
    "seconds":[
      {
        "second":54,
        "data":{
         "field1":7.373158,
         "entry_id":4635,
         "field3":0.19,
         "field2":88
       }
      }
    ]
  },
  {
    "minute":2,
    "seconds":...
  }
]

现在您可以轻松查询具有传感器读数的项目:“field1”>2。

db.col.aggregate(
  [{"$match":{"v.seconds.data.field1":{"$gt":2}}},
   {"$unwind":"$v"}, 
   {"$match":{"v.seconds.data.field1":{"$gt":2}}},
   {"$unwind":"$v.seconds"}, 
   {"$match":{"v.seconds.data.field1":{"$gt":2}}},
   {"$project":{"data":"$v.seconds.data"}}]
)

或者,您可以按分钟拆分文档。类似的东西

"v":[
  {
    "second":1,
    "data":{
       "field1":7.373158,
       "entry_id":4635,
       "field3":0.19,
       "field2":88
     }
  },
  {
     "second":2,
     "data":...
  }
]

您现在可以查询(使用 v.data.field1 上的索引)

db.col.aggregate(
  [{"$match":{"v.data.field1":{"$gt":2}}},
   {"$unwind":"$v"}, 
   {"$match":{"v.data.field1":{"$gt":2}}},
   {"$project":{"data":"$v.data"}}]
)

您可以查询具有传感器读数的项目:“field1”>2 和“field3”>5

使用第一个结构

db.col.aggregate(
  [{"$match":{"v":{"$elemMatch":{"seconds": {$elemMatch:{"field1":{$gt":2},"field3":{$gt":5}}}}}}},
  {"$unwind":"$v"}, 
    {"$match":{"v.seconds": {$elemMatch:{"field1":{$gt":2},"field3":{$gt":5}}}}},
  {"$unwind":"$v.seconds"}, 
  {"$project":{"data":"$v.seconds.data"}}]
)

使用第二种结构

db.col.aggregate(
  [{"$match":{"v.data":{$elemMatch:{"field1":{$gt":2},"field3":{$gt":5}}}}},
  {"$unwind":"$v"}, 
  {"$match":{"v.data.field1":{"$gt":2},"v.data.field3":{"$gt":5} }},
  {"$project":{"data":"$v.data"}}]
)

Mongo 更新 3.6

$match$expr 接受聚合表达式。

$gt > 0 - 聚合表达式检查一分钟内所有匹配秒条件的总和大于 0 的位置

$objectToArray 将命名键转换为键值对,后跟$filter 秒输入条件并输出匹配秒记录的数量。

db.testcol.aggregate(
{"$match":{
  "$expr":{
    "$gt":[
      {"$sum":{
        "$map":{
          "input":{"$objectToArray":"$v"},
          "as":"secondsofminute",
          "in":{
            "$size":{
              "$filter":{
                "input":{"$objectToArray":"$$secondsofminute.v"},
                "as":"seconds",
                "cond":{"$gt":["$$seconds.v.field2",2]}
              }
            }
          }
        }
      }},
    0]
  }
}})

Mongo 更新 3.4 - 将 $expr 替换为 $redact

db.col.aggregate(
 {"$redact":{
  "$cond":{
    "if":{
      "$gt":[
        {"$sum":{
          "$map":{
            "input":{"$objectToArray":"$v"},
            "as":"secondsofminute",
            "in":{
              "$size":{
                "$filter":{
                  "input":{"$objectToArray":"$$secondsofminute.v"},
                  "as":"seconds",
                  "cond":{"$gt":["$$seconds.v.field2",2]}
                }
              }
            }
          }
        }},
        0]
    },
   "then":"$$KEEP",
   "else":"$$PRUNE"
  }
}})

【讨论】:

  • 感谢您的回复,但目前我们不想更改数据存储结构,因为系统已经上线。您提出的结构也有优缺点,例如按分钟和秒排序数据会更慢。
  • Np。我添加了 3.6 和 3.4 版本更新。对于较低版本,您必须使用 $where/map-reduce
  • 我们为客户提供实时数据浏览服务,map-reduce 不太可行
  • 恐怕您对于低于 3.4 的版本没有太多选择。服务器升级适合您吗?
  • 可以升级,有哪些查询选项?
猜你喜欢
  • 2015-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-19
  • 2019-10-12
  • 1970-01-01
相关资源
最近更新 更多