繁体   English   中英

查询嵌套对象键

[英]Query against nested object keys

我想做的是,将我的结果列出在历史记录中包含某些日期的位置。

以下是我在mongo中的其中一个文档的示例。 下面的历史记录对象将日期存储为键,将“数字”存储为键。

我需要做的是执行一个查询,该查询将返回历史键(日期)在一定范围内的所有文档。

例如,如果开始日期是1505435121000,结束日期是1505860712000,则将返回以下文档。如果开始日期是1451606400,然后结束日期是1481906300,则将不返回以下文档

{
    "sold": 24,
    "index": "5",
    "searchRange": 1,
    "history": {
        "1505860712000": 103079,
        "1505773195000": 157659,
        "1505694076000": 92157,
        "1505609622000": 47861,
        "1505516353000": 78869,
        "1505435121000": 158278,
        "1505343796000": 229944
    },
    "createdAt": {
        "$date": "2017-09-20T17:18:49.665Z"
    },
    "updatedAt": {
        "$date": "2017-10-20T08:02:47.094Z"
    },
}

我目前正在做的是提取所有文档,然后对其进行过滤。 但是,随着10k +文档的增长,这将花费非常长的时间,并且变得非常占用CPU且效率低下

MongoDB不能很好地处理遍历对象键,因此最好将数据以“数组”形式进行处理,使其更自然地处理

如果您拥有MongoDB 3.4.4或更高版本,则可以在转换中应用$objectToArray以启用条件:

Model.native((err,collection) => {

  collection.aggregate([
    { "$redact": {
      "$cond": {
        "if": {
          "$gt": [
            { "$size": {
              "$filter": {
                "input": { "$objectToArray": "$history" },
                "as": "h",
                "cond": {
                  "$and": [
                    { "$gte": [ "$$h.k", startDate.toString() ] },
                    { "$lte": [ "$$h.k", endDate.toString() ] }
                  ]
                }
              }
            }},
            0
          ]
        },
        "then": "$$KEEP",
        "else": "$$PRUNE"
      }
    }}
  ])
  .toArray((err,results) => {
    // do something with results
  })
})

简而言之, $objectToArray将“对象”转换为“数组”(以防命名不清晰),每个对象的属性分别为“键”的"k" ”和“值”的"v"条目。 然后,该“数组”被馈送到$filter ,后者应用条件以查看每个条目是否在提供的标准之内。 如果是这样,则将其返回,如果不是,则将其从返回的数组中剥离。

数组的最终$size告诉您是否有任何元素满足条件,并且用$cond表示此逻辑条件,以确定是通过$redact管道的结果来$$KEEP文档还是$$PRUNE文档。

如果没有可用的版本和运算符,则需要使用JavaScript评估$where处理查询:

Model.native((err,collection) => {

  collection.find({ "$where": function() {
    var startDate =  1505435121000,
        endDate = 1505860712000;

    return Object.keys(this.history).some( k =>
      k >= startDate.toString() && k <= endDate.toString()
    )
  }})
  .toArray((err,results) => {
    // do something with results
  })
})

请注意,您需要在上下文中提供函数内部的变量,因为这是将表达式发送到服务器的方式。 因此,您可能想做的就是创建一个函数,该函数将这些函数作为参数,并返回一个实际上可以作为参数传递给$where子句的函数。

您甚至可能会发现一个更简单的概念,即传输到服务器的方式实际上是“字符串”,因此,您实际上可以根据需要将整个JavaScript表达式构造为字符串。 只要它在服务器上进行评估而没有错误,就可以了。

它仍然具有相同的概念,因为Object.keys从对象中提取“ keys”,并且如果这些“ keys”中的“ any”属于那些条件范围内,则Array.some()返回true。


变更资料

如前所述,MongoDB在对象键方面无法很好地工作。 因此,通常最好将这种数据呈现在数组中:

'history': [
  { "time": 150586071200, "value": 103079 },
  { "time": 1505773195000, "value": 157659 }
]

如果您确实有该查询,那么您的查询实际上非常简单。 在此阶段只需编写“本地” mongodb部分:

var startDate =  1505435121000,
        endDate = 1505860712000;

collection.find({ 
  "history": {
    "$elemMatch": {
      "time": { "$gte": startDate, "$lte": endDate }
    }
  }
})

就是这样。 更重要的是,您实际上可以“索引”数据,以便可以使用索引进行选择。 在上面的其他示例中,这都是不可能的,因为我们正在处理“键”而不是“值”,并且本质上需要为每个文档进行“计算”。

因此,即使您说不能立即执行此操作,也确实有令人信服的理由更改结构。 并请注意,集合中的文档越多,“计算扫描”的实际性能就越差。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM