Group大约需要一下几个参数。
key:用来分组文档的字段。和keyf两者必须有一个
keyf:可以接受一个javascript函数。用来动态的确定分组文档的字段。和key两者必须有一个
initial:reduce中使用变量的初始化
reduce:执行的reduce函数。函数需要返回值。
cond:执行过滤的条件。
finallize:在reduce执行完成,结果集返回之前对结果集最终执行的函数。可选的
在关系数据库中,我们可以在数值型字段上执行包含预定义聚合函数的SQL语句,比如,SUM()、COUNT()、MAX()和MIN()。但是在MongoDB中,需要通过MapReduce功能来实现聚合以及批处理,它跟SQL里用来实现聚合的GROUP BY从句比较类似。下一节将描述关系数据库中SQL方式实现的聚合和相应的通过MongoDB提供的MapReduce实现的聚合。
为了讨论这个主题,我们考虑如下所示的Sales表,它以MongoDB中的反范式形式呈现。
Sales表
# |
列名 |
数据类型 |
1 |
OrderId |
INTEGER |
2 |
OrderDate |
STRING |
3 |
Quantity |
INTEGER |
4 |
SalesAmt |
DOUBLE |
5 |
Profit |
DOUBLE |
6 |
CustomerName |
STRING |
7 |
City |
STRING |
8 |
State |
STRING |
9 |
ZipCode |
STRING |
10 |
Region |
STRING |
11 |
ProductId |
INTEGER |
12 |
ProductCategory |
STRING |
13 |
ProductSubCategory |
STRING |
14 |
ProductName |
STRING |
15 |
ShipDate |
STRING |
我们提供了一个查询的样例集,这些查询使用聚合函数、过滤条件和分组从句,及其等效的MapReduce实现,即MongoDB实现SQL中GROUP BY的等效方式。在MongoDB存储的文档上执行聚合操作非常有用,这种方式的一个限制是聚合函数(比如,SUM、AVG、MIN、MAX)需要通过mapper和reducer函数来定制化实现。
MongoDB没有原生态的用户自定义函数(UDFs)支持。但是它允许使用db.system.js.save命令来创建并保存JavaScript函数,JavaScript函数可以在MapReduce中复用。下表是一些常用的聚合函数的实现。稍后,我们会讨论这些函数在MapReduce任务中的使用。
聚合函数 | Javascript 函数 |
|
db.system.js.save( { _id : "Sum" , value : function(key,values) { var total = 0; for(var i = 0; i < values.length; i++) total += values[i]; return total; }}); |
|
db.system.js.save( { _id : "Avg" , value : function(key,values) { var total = Sum(key,values); var mean = total/values.length; return mean; }}); |
|
db.system.js.save( { _id : "Max" , value : function(key,values) { var maxValue=values[0]; for(var i=1;i |
|
db.system.js.save( { _id : "Min" , value : function(key,values) { var minValue=values[0]; for(var i=1;i |
|
db.system.js.save( { _id : "Variance" , value : function(key,values) { var squared_Diff = 0; var mean = Avg(key,values); for(var i = 0; i < values.length; i++) { var deviation = values[i] - mean; squared_Diff += deviation * deviation; } var variance = squared_Diff/(values.length); return variance; }}); |
|
db.system.js.save( { _id : "Standard_Deviation" , value : function(key,values) { var variance = Variance(key,values); return Math.sqrt(variance); }}); |
SQL和MapReduce脚本在四种不同的用例场景中实现聚合函数的代码片段如下表所示。
1.各地区的平均订单量
下面的查询是用来获取不同地区的平均订单量。
SQL Query |
MapReduce Functions |
|
db.sales.runCommand( { mapreduce : "sales" , |
|
map:function() { // emit function handles the group by emit( { // Key city:this.City, state:this.State, region:this.Region}, // Values this.Quantity); }, |
|
reduce:function(key,values) { var result = Avg(key, values); return result; } |
|
|
|
// Group By is handled by the emit(keys, values) line in the map() function above |
out : { inline : 1 } }); |
2.产品的分类销售总额
下面的查询是用来获取产品的分类销售额,根据产品类别的层级分组。在下面例子中,不同的产品类别作为个体维度,它们也可以被称为更复杂的基于层次的维度。
SQL 查询 |
MapReduce 函数 |
|
db.sales.runCommand( { mapreduce : "sales" , |
|
map:function() { emit( // Key {key0:this.ProductCategory, key1:this.ProductSubCategory, key2:this.ProductName}, // Values this.SalesAmt); }, |
|
reduce:function(key,values) { var result = Sum(key, values); return result; } |
|
|
|
// Group By is handled by the emit(keys, values) line in the map() function above |
out : { inline : 1 } });
|
3. 一种产品的最大利润
下面的查询是用来获取一个给定产品基于过滤条件的最大利润。
SQL查询 |
MapReduce 函数 |
|
db.sales.runCommand( { mapreduce : "sales" , |
|
map:function() { if(this.ProductId==1) emit( { key0:this.ProductId, key1:this.ProductName}, this.Profit); }, |
|
reduce:function(key,values) { var maxValue=Max(key,values); return maxValue; } |
|
|
|
// WHERE condition implementation is provided in map() function |
|
// Group By is handled by the emit(keys, values) line in the map() function above |
out : { inline : 1 } }); |
4. 总量、总销售额、平均利润
这个场景的需求是计算订单的总数、总销售额和平均利润,订单ID在1到10之间,发货时间在2011年的1月1日到12月31日之间。下面的查询是用来执行多个聚合,比如,在指定年份以及指定的不同区域和产品类别范围里订单的总数、总销售额和平均利润。
SQL 查询 |
MapReduce 函数 |
|
db.sales.runCommand( { mapreduce : "sales" , |
|
map:function() { emit( { // Keys region:this.Region, productCategory:this.ProductCategory, productid:this.ProductId}, // Values {quantSum:this.Quantity, salesSum:this.SalesAmt, avgProfit:this.Profit} ); } |
|
reduce:function(key,values) { var result= {quantSum:0,salesSum:0,avgProfit:0}; var count = 0; values.forEach(function(value) { // Calculation of Sum(Quantity) result.quantSum += values[i].quantSum; // Calculation of Sum(Sales) result.salesSum += values[i].salesSum; result.avgProfit += values[i].avgProfit; count++; } // Calculation of Avg(Profit) result.avgProfit = result.avgProfit / count; return result; }, |
|
|
|
|
|
query : { "OrderId" : { "$gt" : 1 }, "OrderId" : { "$lt" : 10 }, "ShipDate" : { "$gt" : "01/01/2011" }, "ShipDate" : { "$lt" : "31/12/2011" }, }, |
|
// Group By is handled by the emit(keys, values) line in the map() function above |
|
limit : 3, |
out : { inline : 1 } }); |
MongoDB的MapReduce功能通过数据库命令来调用。Map和Reduce函数在前面章节里已经使用JavaScript实现。下面是执行MapReduce函数的语法。
db.runCommand( { mapreduce : <collection>, map : <mapfunction>, reduce : <reducefunction> [, query : <query filter object>] [, sort : <sorts the input objects using this key. Useful for optimization, like sorting by the emit key for fewer reduces>] [, limit : <number of objects to return from collection>] [, out : <see output options below>] [, keeptemp: <true|false>] [, finalize : <finalizefunction>] [, scope : <object where fields go into javascript global scope >] [, jsMode : true] [, verbose : true] } ) Where the Output Options include: { replace : "collectionName" } { merge : "collectionName" { reduce : "collectionName" } { inline : 1}
|
创建并保存聚合函数
通过MongoDB命令行窗口执行如下命令:
> db.system.js.save( { _id : "Sum" , value : function(key,values) { var total = 0; for(var i = 0; i < values.length; i++) total += values[i]; return total; }});
在示例表Sales表上执行MapReduce程序
> db.sales.runCommand( { mapreduce : "sales" , map:function() { emit( {key0:this.ProductCategory, key1:this.ProductSubCategory, key2:this.ProductName}, this.SalesAmt); }, reduce:function(key,values) { var result = Sum(key, values); return result; } out : { inline : 1 } });
输出如下:
"results" : [ { "_id" : { "key0" : "BT", "key1" : "hardware", "key2" : "CRUD" }, "value" : 400 }, { "_id" : { "key0" : "BT", "key1" : "services", "key2" : "VOCI" }, "value" : 200 }, { "_id" : { "key0" : "IT", "key1" : "hardware", "key2" : "HIM" }, "value" : 200 }, { "_id" : { "key0" : "IT", "key1" : "software", "key2" : "Grad" }, "value" : 200 } ], "timeMillis" : 1, "timing" : { "mapTime" : NumberLong(1), "emitLoop" : 1, "total" : 1 }, "counts" : { "input" : 5, "emit" : 5, "output" : 4 },"ok" : 1