在 mongodb 聚合框架中执行 case-statement
Posted
技术标签:
【中文标题】在 mongodb 聚合框架中执行 case-statement【英文标题】:Performing case-statement in mongodb aggregation framework 【发布时间】:2013-03-19 07:53:40 【问题描述】:我正在评估 MongoDB 聚合框架是否适合我们的需求,因为我们目前在 SQL Server 之上运行。我很难执行特定查询:
假设我有以下伪记录(建模为 sql 表中的列和 mongodb 集合中的完整文档)
name: 'A',
timespent: 100,
,
name: 'B',
timespent: 200,
,
name: 'C',
timespent: 300,
,
name: 'D',
timespent: 400,
,
name: 'E',
timespent: 500,
我想将时间字段分组到范围中并计算出现次数,这样我就会得到例如以下伪记录:
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 来查询单个范围的结果(即获取单个范围的结果),但我无法理解如何在单个管道查询中提取所有范围及其计数。
【问题讨论】:
这个链接可能对你有帮助***.com/questions/8945766/… 【参考方案1】:与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(string))
生成器如下所示:
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
]);
这将返回与上面相同的结果。
【讨论】:
感谢您的回答。我从来没有想过 concat,但它确实简化了一些事情。 虽然此答案解决了问题的特定情况,但请注意,此方法仅支持独占条件。如果某些结果属于 1 个以上的组,则不会将它们累加到每个组中,而是将它们加到一个额外的组中'<group_name1><group_name2>'
【参考方案2】:
从 MongoDB 3.4 开始,我们可以使用 $switch
运算符在 $project
阶段执行多开关语句。
$group
管道运算符按“范围”对文档进行分组,并使用 $sum
累加器运算符返回每个组的“计数”。
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 中的文档进行排序。
【讨论】:
以上是关于在 mongodb 聚合框架中执行 case-statement的主要内容,如果未能解决你的问题,请参考以下文章