查询嵌套对象键

时间:2017-10-20 08:54:56

标签: javascript mongodb sails.js aggregation-framework waterline

我要做的是,将结果列出历史记录包含特定日期的位置。

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

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

例如,如果开始日期为1505435121000且结束日期为1505860712000,则会返回以下文档。如果开始日期为1451606400,结束日期为148​​1906300,则不会返回以下文档

{
    "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并且效率低下

1 个答案:

答案 0 :(得分:2)

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将“对象”转换为“数组”(以防命名不明确),其中“{1”}的属性为"k""v"来自每个对象条目的“值”。然后将该“数组”馈送到$filter,其应用条件以查看每个条目是否落入所提供的标准内。如果是,则返回,如果没有,则从返回的数组中删除它。

数组的最终$size会告诉您是否有符合条件的元素,并且此逻辑条件用$cond表示,以确定是$$KEEP文档还是$$PRUNE它来自$redact管道中的结果。

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

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从对象中提取“键”,如果那些“键”中的“任意”落在这些条件的范围内,则Array.some()返回true。 / p>

更改数据

如前所述,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 }
    }
  }
})

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

所以,即使你说你现在不能这样做,确实有一个令人信服的理由来改变结构。请注意,集合中的文档越多,“计算扫描”的性能就越差。