2016-03-08 77 views
2

我有一个文件,如果我想指望詹姆斯多少书拥有与1990年,看起来像这样MongoDB:计算一个数组中有多少具有给定值的项目,这是在一个文档中?

{ 
    name : james, 
    books : [ 
    { 
     title: title1, 
     year: 1990 
    }, 
    { 
     title: title2, 
     year: 1990 
    }, 
    { 
     title: title3, 
     year: 1991 
    } 
    ] 
} 

说,我怎么会去这样做?我试过以下。但是我意识到这是行不通的,因为'书'是一个数组。

db.collection(collectionName).find({name:james, books: {year: 1990}}).count(function(book_count){ 
    console.log(book_count); 
    } 

任何指针将不胜感激。谢谢!

编辑:

我没有看到另一种答案比你可以用下面这段代码来获得整个数组的大小。但我想知道如何获得具有特定参数的数组中的项的数量。即。而不是看詹姆斯拥有多少本书。我想知道有多少詹姆斯的书发表在1990年

db.mycollection.aggregate({$project: { count: { $size:"$foo" }}}) 
+0

的可能的复制[MongoDB的:计数项的数组中的数](http://stackoverflow.com/questions/21387969/mongodb-count-the-number-of-an-an-an-array) –

+2

严重的是,如果只有一个文档要从这个返回,那么聚合框架(它可以从数组中获取大小并将其过滤掉首先也是)在这里将是一个糟糕的选择。更好的方法是仅测试匹配条件的文档中返回的数组大小。 'db.collection(collectionName).findOne({“name”:“james”,“books.year”:1990},function(err,result){console.log(result.books.filter(function(book){ return book.year == 1990})。length)})'。很简单的东西。只有在您打算使用该数字时才会汇总,以及使用该数字进行汇总。 –

回答

2

聚合框架是这样的理想选择。考虑运行以下管道以获得所需的结果。

pipeline = [ 
    { 
     "$match": { 
      "name": "james", 
      "books.year": 1990 
     } 
    }, 
    { 
     "$project": { 
      "numberOfBooks": { 
       "$size": {     
        "$filter": { 
         "input": "$books", 
         "as": "el", 
         "cond": { "$eq": [ "$$el.year", 1990 ] } 
        }     
       } 
      } 
     } 
    } 
]; 
db.collection.pipeline(pipeline); 

上述管道使用可用MongoDB的3.2新​​操作者以产生满足指定条件,即它过滤不满足标准外元素的数组。流水线初始化为流水线优化策略,以尽早过滤掉进入聚合流水线的文档。

The $size运算符接受单个表达式作为参数,然后给你在结果数组中的元素数,因此你有你想要的书数。


对于不使用早期版本没有找到​​操作,请考虑以下流水线操作的替代解决方案:

pipeline = [ 
    { 
     "$match": { 
      "name": "james", 
      "books.year": 1990 
     } 
    }, 
    { 
     "$project": { 
      "numberOfBooks": { 
       "$size": {     
        "$setDifference": [ 
         { 
          "$map": { 
           "input": "$books", 
           "as": "el", 
           "in": { 
            "$cond": [ 
             { "$eq": [ "$$el.year", 1990 ] }, 
             "$$el", 
             false 
            ] 
           } 
          } 
         }, 
         [false] 
        ]     
       } 
      } 
     } 
    } 
]; 
db.collection.pipeline(pipeline); 

$project流水线阶段涉及fittering书籍数组以便删除1990年以前的文件。这可以通过$setDifference$map运营商。

$map操作在本质上创建保持值作为一个子表达式到数组的每个元素的逻辑评价的结果的新的数组字段。 $setDifference运算符然后返回一个集合,其中元素出现在第一个集合中,但不出现在第二个集合中;即执行第二组相对于第一组的相对补偿。在这种情况下,它将返回包含1990年元素的最终书籍数组,然后$size计算结果数组中的元素数量,从而为您提供书籍数量。


对于使用$unwind操作者,铭记(由于从@BlakesSeven评价此见地响应)中的溶液:

由于只有返回单个文档除了一个空值 键和一个计数之外,没有更多的机会破坏这个限制 比以前的操作具有相同的输出。这并不是说 $ unwind“打破了限制”,而是它“为每个阵列条目生成每个 文档的副本”,其使用更多的存储器(可能的存储器 对总存储器的10%的聚合流水线上限),因此也是 需要“时间”产生以及“时间”来处理。

和作为最后的手段,运行下面的管道:

pipeline = [ 
    { 
     "$match": { 
      "name": "james", 
      "books.year": 1990 
     } 
    }, 
    { "$unwind": "$books" }, 
    { 
     "$match": { "books.year": 1990 } 
    }, 
    { 
     "$group": { 
      "_id": null 
      "count": { "$sum": 1 } 
     } 
    } 
] 
db.collection.pipeline(pipeline) 
+0

这里不使用'$ unwind'的原因不包括16MB的BSON限制。由于只有一个文档只有一个'null'键和一个计数返回,所以与前面的操作相同的输出没有更多的机会打破这个限制。这并不是说'$ unwind'“打破了限制”,它是*“为每个数组条目生成每个文档的副本”*,它使用更多的内存(聚集管线上可能的内存上限为10%的总内存),因此也是需要“时间”产生以及“时间”来处理。 –

+0

您似乎也复制了另一个帖子中的'$ map' /'$ setDifference'示例,因为这些字段与此问题无关。 –

+0

@BlakesSeven是的,它来自我的答案http://stackoverflow.com/a/34763109/122005。感谢您的更正。 – chridam

0

您可以使用$elemMatch投影到只与符合条件的图书检索文档。

db.collection(collectionName).findOne({name:james, books: {year: 1990}}, { books: { $elemMatch: { year: 1990 } } }). // returned document will only contains books having the year 1990. 

如果你只想计数,那么你需要使用aggregation框架。首先匹配文件,然后展开书籍阵列,然后匹配year字段。像下面的东西应该工作:

db.collection(collectionName).aggregate([{$match: {name: "james"}}, {$unwind:"$books"}, {$match:{"books.year":1990}}] 
+0

'$ elemMatch'只会像所有投影算子一样返回**一个**匹配。 –

相关问题