查询嵌套对象键
Query against nested object keys
我想做的是让我的结果列出历史包含特定日期的位置。
下面是我在 mongo 中的文档之一的示例。
下面的历史对象存储日期作为键,'number' 作为键。
我需要做的是执行一个查询,该查询将 return 历史键(日期)在特定范围内的所有文档。
例如,如果开始日期为 1505435121000,结束日期为 1505860712000,则以下文档将被 returned。如果开始日期为 1451606400,结束日期为 1481906300,则以下文档不会被return编辑
{
"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 并没有真正很好地处理遍历对象键,因此最好将数据以 "array" 形式处理,这样处理起来更自然
如果您有 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
将 "object" 转换为具有 "key" 属性的 "array"(以防命名不明确) =] 和 "v"
来自每个对象条目的 "value"。这个 "array" 然后被馈送到 $filter
,它应用条件来查看每个条目是否符合提供的标准。如果是,则将其 returned,如果不是,则将其从 returned 数组中删除。
最终的 $size
of the array tells you if any elements met the conditions, and this logical condition is expressed with $cond
to determine whether to $$KEEP
the document or $$PRUNE
it from results in the $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
})
})
请注意,您需要在上下文中提供函数内的变量,因为这是将表达式发送到服务器的方式。所以你可能想要做的是创建一个函数,它将这些作为参数和 return 一个实际上可以作为参数提供给 $where
子句的函数。
您甚至可能会发现传输到服务器的方式实际上是一个 "string" 是一个更简单的概念,因此您实际上可以将整个 JavaScript 表达式构造为一个字符串,如果你要。只要它在服务器上评估没有错误就没问题。
它仍然具有相同的概念,尽管 Object.keys
extracts the "keys" from the object and Array.some()
如果 "keys" 中的 "any" 落在这些条件的范围内,则 return 为真。
正在更改数据
如前所述,MongoDB 本身不适用于对象键。因此,通常最好在数组中显示此类数据:
'history': [
{ "time": 150586071200, "value": 103079 },
{ "time": 1505773195000, "value": 157659 }
]
如果你真的有那个,那么你的查询实际上非常简单。在这个阶段只写 "native" mongodb 部分:
var startDate = 1505435121000,
endDate = 1505860712000;
collection.find({
"history": {
"$elemMatch": {
"time": { "$gte": startDate, "$lte": endDate }
}
}
})
实际上就是这样。更重要的是,您实际上可以 "index" 数据,因此可以使用索引进行选择。在上面的其他示例中,这都是不可能的,因为我们正在处理 "keys" 而不是 "values" 以及本质上需要为每个文档 "compute"。
因此,即使您说您现在不能这样做,也确实有一个令人信服的理由来更改结构。请注意,集合中的文档越多,"computational scan" 的实际性能就越差。
我想做的是让我的结果列出历史包含特定日期的位置。
下面是我在 mongo 中的文档之一的示例。 下面的历史对象存储日期作为键,'number' 作为键。
我需要做的是执行一个查询,该查询将 return 历史键(日期)在特定范围内的所有文档。
例如,如果开始日期为 1505435121000,结束日期为 1505860712000,则以下文档将被 returned。如果开始日期为 1451606400,结束日期为 1481906300,则以下文档不会被return编辑
{
"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 并没有真正很好地处理遍历对象键,因此最好将数据以 "array" 形式处理,这样处理起来更自然
如果您有 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
将 "object" 转换为具有 "key" 属性的 "array"(以防命名不明确) =] 和 "v"
来自每个对象条目的 "value"。这个 "array" 然后被馈送到 $filter
,它应用条件来查看每个条目是否符合提供的标准。如果是,则将其 returned,如果不是,则将其从 returned 数组中删除。
最终的 $size
of the array tells you if any elements met the conditions, and this logical condition is expressed with $cond
to determine whether to $$KEEP
the document or $$PRUNE
it from results in the $redact
管道。
如果您没有可用的版本和运算符,则需要使用 $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
})
})
请注意,您需要在上下文中提供函数内的变量,因为这是将表达式发送到服务器的方式。所以你可能想要做的是创建一个函数,它将这些作为参数和 return 一个实际上可以作为参数提供给 $where
子句的函数。
您甚至可能会发现传输到服务器的方式实际上是一个 "string" 是一个更简单的概念,因此您实际上可以将整个 JavaScript 表达式构造为一个字符串,如果你要。只要它在服务器上评估没有错误就没问题。
它仍然具有相同的概念,尽管 Object.keys
extracts the "keys" from the object and Array.some()
如果 "keys" 中的 "any" 落在这些条件的范围内,则 return 为真。
正在更改数据
如前所述,MongoDB 本身不适用于对象键。因此,通常最好在数组中显示此类数据:
'history': [
{ "time": 150586071200, "value": 103079 },
{ "time": 1505773195000, "value": 157659 }
]
如果你真的有那个,那么你的查询实际上非常简单。在这个阶段只写 "native" mongodb 部分:
var startDate = 1505435121000,
endDate = 1505860712000;
collection.find({
"history": {
"$elemMatch": {
"time": { "$gte": startDate, "$lte": endDate }
}
}
})
实际上就是这样。更重要的是,您实际上可以 "index" 数据,因此可以使用索引进行选择。在上面的其他示例中,这都是不可能的,因为我们正在处理 "keys" 而不是 "values" 以及本质上需要为每个文档 "compute"。
因此,即使您说您现在不能这样做,也确实有一个令人信服的理由来更改结构。请注意,集合中的文档越多,"computational scan" 的实际性能就越差。