2013-03-19 24 views
14

我正在评估MongoDB聚合框架如何满足我们的需求,因为我们当前正在SQL Server上运行。我有一个很难执行特定的查询:在mongodb聚合框架中执行case-statement

说我有下面的伪记录(在MongoDB中集建模为列在SQL表,并作为一个完整的文档)

{ 
    name: 'A', 
    timespent: 100, 
}, 
{ 
    name: 'B', 
    timespent: 200, 
}, 
{ 
    name: 'C', 
    timespent: 300, 
}, 
{ 
    name: 'D', 
    timespent: 400, 
}, 
{ 
    name: 'E', 
    timespent: 500, 
} 

我想将timepent字段分组到范围中并计算出现次数,以便我得到例如下面的伪记录:

results{ 
    0-250: 2, 
    250-450: 2, 
    450-650: 1 
} 

注意,这些范围(250,450和650)是动态的,并很可能由用户随时间改变。在SQL我们抽取的是这样的结果:

select range, COUNT(*) as total from (
select case when Timespent <= 250 then '0-250' 
when Timespent <= 450 then '200-450' 
else '450-600' end as range 
from TestTable) as r 
group by r.range 

再次注意,这个SQL是通过我们的应用程序,以适应可在任一个时刻的特定范围动态构造。

我努力在mongodb聚合框架中找到适当的构造来执行这样的查询。我可以通过在流水线中插入$ match来查询单个范围的结果(即得到单个范围的结果),但我无法理解如何在单个流水线查询中提取所有范围及其计数。

+0

可能是此链接将帮助您 http://stackoverflow.com/questions/8945766/mongodb-mysql-sum-case-when-equivalent/21700596#21700596 – 2014-02-11 11:39:46

+1

http://www.codefari.com/2015 /08/case-statement-in-mongodb-query.html 上面的链接可能会帮助您 – Singh 2015-08-26 02:07:36

回答

28

什么对应于aggregation framework中的“case”SQL语句,是$ cond操作符(请参阅manual)。 $ cond语句可以嵌套以模拟“when-then”和“else”,但我选择了另一种方法,因为它更易于阅读(并生成,见下文):我将使用$ concat运算符来编写范围字符串,然后作为分组键。

因此,对于给定的集合:

db.xx.find() 
{ "_id" : ObjectId("514919fb23700b41723f94dc"), "name" : "A", "timespent" : 100 } 
{ "_id" : ObjectId("514919fb23700b41723f94dd"), "name" : "B", "timespent" : 200 } 
{ "_id" : ObjectId("514919fb23700b41723f94de"), "name" : "C", "timespent" : 300 } 
{ "_id" : ObjectId("514919fb23700b41723f94df"), "name" : "D", "timespent" : 400 } 
{ "_id" : ObjectId("514919fb23700b41723f94e0"), "name" : "E", "timespent" : 500 } 

骨料(硬编码)看起来是这样的:

db.xx.aggregate([ 
    { $project: { 
    "_id": 0, 
    "range": { 
     $concat: [{ 
     $cond: [ { $lte: ["$timespent", 250] }, "range 0-250", "" ] 
     }, { 
     $cond: [ { $and: [ 
      { $gte: ["$timespent", 251] }, 
      { $lt: ["$timespent", 450] } 
     ] }, "range 251-450", "" ] 
     }, { 
     $cond: [ { $and: [ 
      { $gte: ["$timespent", 451] }, 
      { $lt: ["$timespent", 650] } 
     ] }, "range 450-650", "" ] 
     }] 
    } 
    }}, 
    { $group: { _id: "$range", count: { $sum: 1 } } }, 
    { $sort: { "_id": 1 } }, 
]); 

,其结果是:

{ 
    "result" : [ 
     { 
      "_id" : "range 0-250", 
      "count" : 2 
     }, 
     { 
      "_id" : "range 251-450", 
      "count" : 2 
     }, 
     { 
      "_id" : "range 450-650", 
      "count" : 1 
     } 
    ], 
    "ok" : 1 
} 

为了产生集合命令,您必须将“范围”投影构建为JSON对象(或者您可以生成一个字符串和然后使用JSON.parse(字符串))

发电机看起来像这样:

var ranges = [ 0, 250, 450, 650 ]; 
var rangeProj = { 
    "$concat": [] 
}; 

for (i = 1; i < ranges.length; i++) { 
    rangeProj.$concat.push({ 
    $cond: { 
     if: { 
     $and: [{ 
      $gte: [ "$timespent", ranges[i-1] ] 
     }, { 
      $lt: [ "$timespent", ranges[i] ] 
     }] 
     }, 
     then: "range " + ranges[i-1] + "-" + ranges[i], 
     else: "" 
    } 
    }) 
} 

db.xx.aggregate([{ 
    $project: { "_id": 0, "range": rangeProj } 
}, { 
    $group: { _id: "$range", count: { $sum: 1 } } 
}, { 
    $sort: { "_id": 1 } 
}]); 

将返回与上述相同的结果。

+0

感谢您的回答。我从来不会想到concat,但它肯定会简化一些事情。 – 2014-11-06 19:26:29

+1

谢谢简短而有效的答案。 更多http://www.codefari.com/2015/08/case-statement-in-mongodb-query.html – Singh 2015-08-26 02:05:09

+0

虽然这个答案解决了这个问题的特殊情况,但请注意这种方法仅支持排他性条件。如果某些结果落入1个以上的组中,则不会将它们归入其中的每个结果中,而是加入到另一个组中''' – ArnauOrriols 2016-02-13 11:56:46

5

从MongoDB 3.4开始,我们可以使用$switch运算符在$project阶段执行多开关语句。

$group管道运算符使用“范围”对文档进行分组,并使用累加器运算符为每个组返回“计数”。

db.collection.aggregate(
    [ 
     { "$project": { 
      "range": { 
       "$switch": { 
        "branches": [ 
         { 
          "case": { "$lte": [ "$timespent", 250 ] }, 
          "then": "0-250" 
         }, 
         { 
          "case": { 
           "$and": [ 
            { "$gt": [ "$timespent", 250 ] }, 
            { "$lte": [ "$timespent", 450 ] } 
           ] 
          }, 
          "then": "251-450" 
         }, 
         { 
          "case": { 
           "$and": [ 
            { "$gt": [ "$timespent", 450 ] }, 
            { "$lte": [ "$timespent", 650 ] } 
           ] 
          }, 
          "then": "451-650" 
         } 
        ], 
        "default": "650+" 
       } 
      } 
     }}, 
     { "$group": { 
      "_id": "$range", 
      "count": { "$sum": 1 } 
     }} 
    ] 
) 

随着我们收集下列文件,

{ "_id" : ObjectId("514919fb23700b41723f94dc"), "name" : "A", "timespent" : 100 }, 
{ "_id" : ObjectId("514919fb23700b41723f94dd"), "name" : "B", "timespent" : 200 }, 
{ "_id" : ObjectId("514919fb23700b41723f94de"), "name" : "C", "timespent" : 300 }, 
{ "_id" : ObjectId("514919fb23700b41723f94df"), "name" : "D", "timespent" : 400 }, 
{ "_id" : ObjectId("514919fb23700b41723f94e0"), "name" : "E", "timespent" : 500 } 

我们的查询率:

{ "_id" : "451-650", "count" : 1 } 
{ "_id" : "251-450", "count" : 2 } 
{ "_id" : "0-250", "count" : 2 } 

我们可能需要一个$sort阶段添加到管道排序我们的文档范围,但这只会对lexicographic order中的文档进行分类,因为“range ”。