2013-03-19 59 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 」。