在MongoDB中考虑这组数据......
{
_id: 1,
name: "Johnny",
properties: [
{
type: "A",
value: 257,
date: "4/1/2014"
},
{
type: "A",
value: 200,
date: "4/2/2014"
},
{
type: "B",
value: 301,
date: "4/3/2014"
},
...]
}
查询其中一个(或多个)最后两个"属性"的文档的正确方法是什么?元素的值> x,或者最后两个"属性中的一个(或多个)"类型" A"有一个值> X?
答案 0 :(得分:1)
如果你可以修改你的插入方法,请尝试如下;
更改您的更新以推送以下内容:
doc = { type : "A", "value" : 123, "date" : new Date() }
db.foo.update( {_id:1}, { "$push" : { "properties" : { "$each" : [ doc ], "$sort" : { date : -1} } } } )
这将为您提供按时间降序排序的文档数组,使得"最新的"首先是文件。
您现在可以使用标准的MongoDB点表示法来查询0, 1, etc elements of your properties array,它代表了最新的逻辑添加。
答案 1 :(得分:0)
您是否考虑过使用$ where子句?不是最有效的,但我认为它应该得到你想要的。例如,如果您希望每个具有最后两个属性元素值字段大于200的文档,您可以尝试:
db.collection.find({properties:{$exists:true},
$where: "(this.properties[this.properties.length-1].value > 200)||
(this.properties[this.properties.length-2].value > 200)"});
这需要对边缘情况(例如数组< 2成员)和更复杂的查询(通过“类型”字段)进行一些工作,但是应该让你开始。
答案 2 :(得分:0)
根据评论,聚合框架不仅仅是简单地“聚合”值,因此您可以利用各种pipeline operators来完成使用{{3}无法实现的非常高级的事情。 }}
db.collection.aggregate([
// Match documents that "could" meet the conditions to narrow down
{ "$match": {
"properties": { "$elemMatch": {
"type": "A", "value": { "$gt": 200 }
}}
}},
// Keep a copy of the document for later with an array copy
{ "$project": {
"_id": {
"_id": "$_id",
"name": "$name",
"properties": "$properties"
},
"properties": 1
}},
// Unwind the array to "de-normalize"
{ "$unwind": "$properties" },
// Get the "last" element of the array and copy the existing one
{ "$group": {
"_id": "$_id",
"properties": { "$last": "$_id.properties" },
"last": { "$last": "$properties" },
"count": { "$sum": 1 }
}},
// Unwind the copy again
{ "$unwind": "$properties" },
// Project to mark the element you already have
{ "$project": {
"properties": 1,
"last": 1,
"count": 1,
"seen": { "$eq": [ "$properties", "$last" ] }
}},
// Match again, being careful to keep any array with one element only
// This gets rid of the element you already kept
{ "$match": {
"$or": [
{ "seen": false },
{ "seen": true, "count": 1 }
]
}},
// Group to get the second last element as "next"
{ "$group": {
"_id": "$_id",
"last": { "$last": "$last" },
"next": { "$last": "$properties" }
}},
// Then match to see if either of those elements fits
{ "$match": {
"$or": [
{ "last.type": "A", "last.value": { "$gt": 200 } },
{ "next.type": "A", "next.value": { "$gt": 200 } }
]
}},
// Finally restore your matching documents
{ "$project": {
"_id": "$_id._id",
"name": "$_id.name",
"properties": "$_id.properties"
}}
])
详细介绍一下:
第一个.find()
用法是确保您只处理可能“可能”与扩展条件匹配的文档。像这样优化总是一个好主意。
下一阶段是$match
,因为您可能希望保留原始文档的详细信息,并且您至少需要再次使用该数组才能获得第二个元素。
接下来的各个阶段使用$project
来将数组分解为单个文档,然后是$unwind
,后者用于查找文档中的最后一项{{1边界。这实际上是数组中的最后一项。另外,你要保留数组元素的数量。
然后在原始数组内容上再次使用$group
之后,$unwind
的使用再次向文档添加“看到”字段,指示通过使用$ eq运算符是否或者原件中的文件实际上是以前作为“最后”元素的文件。
在该阶段之后,您再次发出$project
以便从结果中过滤掉最后一个文档,同时确保在不删除最初匹配数组长度的任何内容的情况下实际上是1。
从这里你想再次$match
以获得数组中的“倒数第二”元素(或者实际上只有一个元素的“最后”元素。
因此,虽然这是相当复杂的,当然,通过在数组末尾测试的项目数量可以增加复杂性,并且可以显示聚合如何非常适合问题。
在可能的情况下,这是最好的方法,因为调用JavaScript解释器会传递与聚合使用的本机代码相比的开销。
使用mapReduce会删除使用最后两个可能元素(或更多)的代码复杂性,但它会自然地调用JavaScript解释器,因此运行速度会慢得多。
对于记录,由于问题中的示例不是匹配,因此这里有一些数据将匹配最后两个文档,其中一个数据只有一个元素:< / p>
_id