MongoDB Aggregate Methods(2) MonoDB 的 3 种聚合函数
aggregate(pipeline,options)指定 group 的 keys, 通过操作符$push/$addToSet/$sum等实现简单的 reduce, 不支持函数/自定义变量group({ key, reduce, initial [, keyf] [, cond] [, finalize] })支持函数(keyf)mapReduce的阉割版本mapReduce终极大杀器count(query)too young too simpledistinct(field,query)
最后两个操作很简单, 这里主要比较前面 3 个操作
样本数据
- created 时间戳(ms)
- group 组别 [A-Z]
- category 目录 C[1-10]
- count
- title 26 个字母随机组成
{'group': 'E', 'created': 1402764223433, 'count': 63, 'datetime': datetime.datetime(2014, 6, 15, 0, 43, 43, 433000), 'title': 'KNBVHICLGOUESDFYTJWRXMPZQA', 'category': 'C8'}
{'group': 'I', 'created': 1413086660063, 'count': 93, 'datetime': datetime.datetime(2014, 10, 12, 12, 4, 20, 62000), 'title': 'UJZXCIHBFKELPYVWGAQRMNOSDT', 'category': 'C10'}
{'group': 'H', 'created': 1440750343730, 'count': 41, 'datetime': datetime.datetime(2015, 8, 28, 16, 25, 43, 730000), 'title': 'HSDKQPFMNBJEGXYZARITCVOWLU', 'category': 'C1'}
{'group': 'S', 'created': 1437710373637, 'count': 14, 'datetime': datetime.datetime(2015, 7, 24, 11, 59, 33, 637000), 'title': 'ZFJITUDHMBRLGKNEWSCPXVOYQA', 'category': 'C10'}
{'group': 'Z', 'created': 1428307315436, 'count': 78, 'datetime': datetime.datetime(2015, 4, 6, 16, 1, 55, 436000), 'title': 'FUYXWOQZPTKMSCNGDHEJVBILAR', 'category': 'C5'}
{'group': 'R', 'created': 1402809274964, 'count': 74, 'datetime': datetime.datetime(2014, 6, 15, 13, 14, 34, 963000), 'title': 'JKZCMPIAONDTWRQLHGUEVSFXYB', 'category': 'C9'}
{'group': 'Y', 'created': 1400571321919, 'count': 66, 'datetime': datetime.datetime(2014, 5, 20, 15, 35, 21, 918000), 'title': 'KSPGZJDMHNUALCEWBYXVIQOTFR', 'category': 'C2'}
{'group': 'L', 'created': 1416562128208, 'count': 5, 'datetime': datetime.datetime(2014, 11, 21, 17, 28, 48, 207000), 'title': 'ZHDLBRMNPXEVAQKJYSITCFGWUO', 'category': 'C1'}
{'group': 'E', 'created': 1414057884852, 'count': 12, 'datetime': datetime.datetime(2014, 10, 23, 17, 51, 24, 851000), 'title': 'EBZKXDTOQSCYJAGFPVIHNRULMW', 'category': 'C3'}
{'group': 'L', 'created': 1418879346846, 'count': 67, 'datetime': datetime.datetime(2014, 12, 18, 13, 9, 6, 845000), 'title': 'DZMQRXYVHOJFUGENCASTLWBPKI', 'category': 'C3'}
aggregate
接受两个参数 pipeline/options, pipeline 是 array, 相同的 operator 可以多次使用
pipeline 支持的方法
$geoNear不予测试$group指定 group 的_id(key/keys) 和基于操作符($push/$sum/...) 的累加运算$limit限制输出$match输入过滤条件$out将输出结果保存到collection$project修改数据流中的文档结构$redact是$project/$match功能的合并$skip$sort对结果排序$unwind拆解数据
$group 允许用的累加操作符 $addToSet/$avg/$first/$last/$max/$min/$push/$sum
不被允许的累加操作符 $each...$group 操作默认最多可以用 100MB RAM, 增加 allowDiskUse 可以让 $group 操作更多的数据
下面是一个揉进全部特性的用法
db.data.aggregate([
{$match: {}},
{$skip: 10}, // 跳过 collection 的前 10 行
// 选择需要的字段
{$project: {group: 1, datetime: 1, category: 1, count: 1}},
// 如果不选择 {count: 1} 最后的结果中 count_all/count_avg = 0
// {$project: {group: 1, datetime: 1, category: 1}},
{$redact: { // redact 简单用法 过滤 group != 'A' 的行
$cond: [{$eq: ["$group", "A"]}, "$$DESCEND", "$$PRUNE"]
}},
{$group: {
_id: {year: {$year: "$datetime"}, month: {$month: "$datetime"}, day: {$dayOfMonth: "$datetime"}},
group_unique: {$addToSet: "$group"},
category_first: {$first: "$category"},
category_last: {$last: "$category"},
count_all: {$sum: "$count"},
count_avg: {$avg: "$count"},
rows: {$sum: 1}
}},
// 拆分 group_unique 如果开启这个选项, 会导致 _id 重复而无法写入 out 指定的 collection, 除非再 $group 一次
// {$unwind: "$group_unique"},
// 只保留这两个字段
{$project: {group_unique: 1, rows: 1}},
// 结果按照 _id 排序
{$sort: {"_id": 1}},
// 只保留 50 条结果
// {$limit: 50},
// 结果另存
{$out: "data_agg_out"},
], {
explain: true,
allowDiskUse: true,
cursor: {batchSize: 0}
})
db.data_agg_out.find()
db.data_agg_out.aggregate([
{$group: {
_id: null,
rows: {$sum: '$rows'}
}}
])
db.data_agg_out.drop()
$match聚合前数据筛选$skip跳过聚合前数据集的 n 行, 如果{$skip: 10}, 最后rows = 5000000 - 10$project之选择需要的字段, 除了_id之外其他的字段的值只能为 1$redact看了文档不明其实际使用场景, 这里只是简单筛选聚合前的数据$group指定各字段的累加方法$unwind拆分 array 字段的值, 这样会导致_id重复$project可重复使用多次 最后用来过滤想要存储的字段$out如果$group/$project/$redact的_id没有重复就不会报错- 以上方法中
$project/$redact/$group/$unwind可以使用多次
group
group 比 aggregate 好的一个地方是 map/reduce 都支持用 function 定义, 下面是支持的选项
ns如果用db.runCommand({group: {}})方式调用, 需要ns指定 collectioncond聚合前筛选key聚合的 keyinitial初始化 累加 结果$reduce接受(curr, result)参数, 将curr累加到resultkeyf代替key用函数生成聚合用的主键finalize结果处理
需要保证输出结果小于 16MB 因为 group 没有提供转存选项
db.data.group({
cond: {'group': 'A'},
// key: {'group': 1, 'category': 1},
keyf: function(doc) {
var dt = new Date(doc.created);
// or
// var dt = doc.datetime;
return {
year: doc.datetime.getFullYear(),
month: doc.datetime.getMonth() + 1,
day: doc.datetime.getDate()
}
},
initial: {count: 0, category: []},
$reduce: function(curr, result) {
result.count += curr.count;
if (result.category.indexOf(curr.category) == -1) {
result.category.push(curr.category);
}
},
finalize: function(result) {
result.category = result.category.join();
}
})
如果要求聚合大量数据, 就需要用到 mapReduce
mapReduce
先看看它支持的特性/选项
query聚合前筛选sort对聚合前的数据排序 用来优化 reducelimit限制进入 map 的数据map(function) emit(key, value) 在函数中指定聚合的 K/Vreduce(function) 参数(key, values)key在 map 中定义了,values是在这个 K 下的所有 V 数组finalize处理最后结果out结果转存 可以选择另外一个 dbscope设置全局变量jdMode(false) 是否(默认是)把 map/reduce 中间结果转为 BSON 格式, BSON 格式可以利用磁盘空间, 这样就可以处理大规模的数据集verbose(true) 详细信息
如果设 jsMode 为 true 不进行 BSON 转换, 可以优化 reduce 的执行速度, 但是由于内存限制最大在 emit 数量小于 500,000 时使用
写 mapReduce 时需要注意
- emit 返回的 value 必须和 reduce 返回的 value 结构一致
reduce函数必须幂等- 详见 Troubleshoot the Reduce Function
db.data.mapReduce(function() {
// var d = new Date(this.created);
var d = this.datetime;
var key = {
year: d.getFullYear(),
month: d.getMonth() + 1,
day: d.getDate(),
};
var value = {
count: this.count,
rows: 1,
groups: [this.group],
}
emit(key, value);
}, function(key, vals) {
var reducedVal = {
count: 0,
groups: [],
rows: 0,
};
for(var i = 0; i < vals.length; i++) {
var v = vals[i];
reducedVal.count += v.count;
reducedVal.rows += v.rows;
for(var j = 0; j < v.groups.length; j ++) {
if (reducedVal.groups.indexOf(v.groups[j]) == -1) {
reducedVal.groups.push(v.groups[j]);
}
}
}
return reducedVal;
}, {
query: {},
sort: {datetime: 1}, // 需要索引 否则结果返回空
limit: 50000,
finalize: function(key, reducedVal) {
reducedVal.avg = reducedVal.count / reducedVal.rows;
return reducedVal;
},
out: {
inline: 1,
// replace: "",
// merge: "",
// reduce: "",
},
scope: {},
jsMode: true
})
总结
| method | allowDiskUse | out | function |
|---|---|---|---|
| aggregate | true | pipeline/collection | false |
| group | false | pipeline | true |
| mapReduce | jsMode | pipeline/collection | true |
aggregate基于累加操作的的聚合 可以重复利用$project/$group一层一层聚合数据, 可以用于大量数据(单输出结果小于 16MB) 不可用于分片数据mapReduce可以处理超大数据集 需要严格遵守 mapReduce 中的结构一致/幂等 写法, 可增量输出/合并, 见outoptionsgroupRDB 中的group by简单需求可用(只有 inline 输出) 会产生read lock- StackOverflow 中关于三者比较的解答
清理
# cleanup
client.drop_database(db)
样本数据生成
这里用 python3 + pandas 生成 500w 条样本数据
from datetime import date, datetime
import string
import pandas as pd
import numpy as np
from pymongo import MongoClient
from bson.objectid import ObjectId
client = MongoClient()
db = client[str(ObjectId())]
# fill data
N = 10000 * 500
dr = pd.date_range('2014-01-01', '2015-12-31', freq='M')
t1, t2 = dr.to_period()[0].start_time.timestamp(), dr.to_period()[-1].end_time.timestamp()
docs = []
bulk_size = 10000
letters = string.ascii_letters[26:]
for t in np.random.randint(t1 * 1000, t2 * 1000, N):
docs.append({
'created': int(t),
'datetime': datetime.fromtimestamp(t / 1000),
'title': ''.join(np.random.permutation(list(letters))),
'group': np.random.choice(list(letters)),
'category': np.random.choice(['C%s' % (i + 1) for i in range(10)]),
'count': int(np.random.randint(0, 100)),
})
if len(docs) >= bulk_size:
db.data.insert(docs)
docs = []
if docs:
db.data.insert(docs)
for row in db.data.find(fields={'_id': 0}).limit(10):
print(row)
MongoDB Aggregate Methods(2) MonoDB 的 3 种聚合函数的更多相关文章
- mongoDB (mongoose、增删改查、聚合、索引、连接、备份与恢复、监控等等)
MongoDB - 简介 官网:https://www.mongodb.com/ MongoDB 是一个基于分布式文件存储的数据库,由 C++ 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储 ...
- MongoDB 聚合函数
概念 聚合函数是对一组值执行计算并返回单一的值 主要的聚合函数 count distinct Group MapReduce 1.count db.users.count() db.users.cou ...
- Mongodb学习笔记四(Mongodb聚合函数)
第四章 Mongodb聚合函数 插入 测试数据 ;j<;j++){ for(var i=1;i<3;i++){ var person={ Name:"jack"+i, ...
- 在MongoDB中实现聚合函数 (转)
随着组织产生的数据爆炸性增长,从GB到TB,从TB到PB,传统的数据库已经无法通过垂直扩展来管理如此之大数据.传统方法存储和处理数据的成本将会随着数据量增长而显著增加.这使得很多组织都在寻找一种经济的 ...
- MDX Step by Step 读书笔记(七) - Performing Aggregation 聚合函数之 Sum, Aggregate, Avg
开篇介绍 SSAS 分析服务中记录了大量的聚合值,这些聚合值在 Cube 中实际上指的就是度量值.一个给定的度量值可能聚合了来自事实表中上千上万甚至百万条数据,因此在设计阶段我们所能看到的度量实际上就 ...
- 在MongoDB中实现聚合函数
在MongoDB中实现聚合函数 随着组织产生的数据爆炸性增长,从GB到TB,从TB到PB,传统的数据库已经无法通过垂直扩展来管理如此之大数据.传统方法存储和处理数据的成本将会随着数据量增长而显著增加. ...
- django的聚合函数和aggregate、annotate方法使用
支持聚合函数的方法: 提到聚合函数,首先我们要知道的就是这些聚合函数是不能在django中单独使用的,要想在django中使用这些聚合函数,就必须把这些聚合函数放到支持他们的方法内去执行.支持聚合函数 ...
- Mongodb聚合函数
插入 测试数据 for(var j=1;j<3;j++){ for(var i=1;i<3;i++){ var person={ Name:"jack"+i, Age: ...
- MongoDB 聚合函数及排序
聚合函数 最大值 $max db.mycol.aggregate([{$group : {_id : "$by_user", num_max : {$max: "$li ...
随机推荐
- ubuntu 14.0.04 通过自启动脚本实现主机和虚拟机的文件夹共享
首先要先设置主机ip为固定ip: 1.vi /etc/rc.local,进入vi界面,在尾部增加一行代码: mount -t cifs -o username=share,password=12345 ...
- hostapd源代码分析(一):网络接口和BSS的初始化
[转]hostapd源代码分析(一):网络接口和BSS的初始化 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004349 最近在做一 ...
- golang操作文件的四种方法
golang追加内容到文件末尾 字数349 阅读54 评论0 喜欢2 golang读写文件,网上很多教程了但是今天有个需求,想要把内容追加写到文件末尾google了好久,没有查到研究了一会儿file库 ...
- 下载Android 5.0源码(附源码)
下载方法见我的另一篇博客:http://blog.csdn.net/maosidiaoxian/article/details/41680769 2014-12-24更新:5.0.1源码已上传. 这次 ...
- JavaScript屏蔽Backspace键
原文:http://www.cnblogs.com/xdp-gacl/p/3785806.html 今天在IE浏览器下发现,当把使用readonly="readonly"属性将文本 ...
- D3.js 交互式操作
与图表的交互,指在图形元素上设置一个或多个监听器,当事件发生时,做出相应的反应. 一.什么是交互 交互,指的是用户输入了某种指令,程序接受到指令之后必须做出某种响应.对可视化图表来说,交互能使图表更加 ...
- 自我总结(六)---(学习j2ee+j2ee第一阶段项目)
自我完善的过程就是在不断的自我总结不断的改进. 学习了Struts2 Spring Hibernate. 十天前结束了这个课程.也考试了.这次考试老师说机试考的还不错.其实就是一个简单的用户登录,进行 ...
- python语法笔记(一)
1. python中多个函数或者类定义可以放在一个.py 文件中,视为一个模块.模块的.py文件中,一般要写 if __name__ == '__mian__' 用来单独执行该模块内的某些函数. 2. ...
- el 和 fmt 常用
EL表达式入门 转自 http://blog.chinaunix.net/uid-9789791-id-1997374.html 隐含对象: pageContext: pageContext对象 pa ...
- javascript强制转换详解
转换成数值 Number函数强制转换成数值 数值->转换成原来的值 字符串->如果可以解析为数值,则转换成数值:否则转换成NaN或者0 true->1,falSe->0 und ...