MongoDB基础之分组聚合函数讲解
<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>1 group<ul><li>1.1 定义</li><li>1.2 操作group<ul><li>1.2.1 求数目</li><li>1.2.2 求总和</li><li>1.2.3 求最大</li><li>1.2.4 求平均</li></ul></li></ul></li><li>2 aggregate<ul><li>2.1 定义</li><li>2.2 操作aggregate<ul><li>2.2.1 $match筛选</li><li>2.2.2 $project映射<ul><li>2.2.2.1 自定义字段</li><li>2.2.2.2 起别名</li><li>2.2.2.3 引用原字段:$字段名</li><li>2.2.2.4 使用算术表达式</li><li>2.2.2.5 字符串截取</li><li>2.2.2.6 字符串拼接</li><li>2.2.2.7 大小写转换</li><li>2.2.2.8 日期表达式</li><li>2.2.2.9 比较非字符串大小</li><li>2.2.2.10 比较字符串大小</li><li>2.2.2.11 比较表达式</li><li>2.2.2.12 And连接</li><li>2.2.2.13 Or连接</li><li>2.2.2.14 not连接</li><li>2.2.2.15 三位运算符</li><li>2.2.2.16 ifNull连接</li></ul></li><li>2.2.3 $group 分组<ul><li>2.2.3.1 单字段分组</li><li>2.2.3.2 对多个字段进行分组</li><li>2.2.3.3 聚合运算</li><li>2.2.3.4 addToSet</li><li>2.2.3.5 push操作</li></ul></li><li>2.2.4 $unwind</li><li>2.2.5 $sort</li><li>2.2.6 $limit</li><li>2.2.7 $skip</li><li>2.2.8 综合使用</li></ul></li><li>2.3 常见聚合表达式</li></ul></li><li>3 mapReduce<ul><li>3.1 mapReduce语法</li><li>3.2 与分组聚合对比</li><li>3.3 使用 mapReduce</li><li>3.4 runCommand<ul><li>3.4.1 语法</li><li>3.4.2 案例</li></ul></li></ul></li></ul></div><br><code>MongoDB</code> 中主要有三个函数:<p></p>
<ul>
<li><code>group</code>:分组统计</li>
<li><code>aggregate</code>:简单聚合</li>
<li><code>mapReduce</code>:强大统计</li>
</ul>
<h1 id="1-group">1 group</h1>
<h2 id="11-定义">1.1 定义</h2>
<p><code>MongoDB</code> 中使用<code>group</code>来进行分组聚合,语法如下:</p>
<pre><code class="language-sql">db.collection.group(document);
</code></pre>
<p>document中信息:</p>
<pre><code class="language-json">{
key:{key1:true,key2:true},
cond:{},
reduce:function(curr,result){},
initial:{},
finalize:function(curr,result){}
}
</code></pre>
<p>document中字段说明:</p>
<ul>
<li><code>key</code>:分组字段,作为分组的key,等价于sql中group by a,b</li>
<li><code>cond</code>:查询条件</li>
<li><code>reduce</code>:聚合函数<br>
一个聚合函数操作文档的分组操作期间。这些函数可以返回一个sum或count。<br>
该函数接受两个参数:<code>当前文档</code>和这个群体聚集的<code>结果文档</code></li>
<li><code>initial</code>:初始化聚合结果文档变量,为空时自动为每列提供初始变量</li>
<li><code>finalize</code>:统计一组后的回调函数<br>
在<code>db.collection.group()</code>返回最终结果之前,此功能可以修改的结果文档或替换的结果文档作为一个整体</li>
</ul>
<p><code>注意</code>:</p>
<ol>
<li><code>group</code>需要我们手写聚合函数的业务逻辑</li>
<li><code>group </code>不支持<code>shard cluster</code>,无法分布式运算</li>
<li>有些版本不支持<code>group</code>函数,可以通过<code>db.collection.help()</code>函数查看</li>
</ol>
<h2 id="12-操作group">1.2 操作group</h2>
<h3 id="121-求数目">1.2.1 求数目</h3>
<pre><code class="language-sql">db.collection.group({
key:{cat_id:1},
cond:{},
reduce:function(curr,result) {
result.cnt += 1;
},
initial:{cnt:0}
})
类似于sql:
selectcount(*) from goods group by cat_id;
</code></pre>
<h3 id="122-求总和">1.2.2 求总和</h3>
<pre><code class="language-sql">db.collection.group({
key:{cat_id:1},
cond:{},
reduce:function(curr,result) {
result.num += curr.goods_number;
},
initial:{num:0}
})
类似于sql:
selectsum(goods_number) from goods group by cat_id;
</code></pre>
<h3 id="123-求最大">1.2.3 求最大</h3>
<pre><code class="language-sql">db.collection.group({
key:{cat_id:1},
cond:{},
reduce:function(curr , result) {
if(curr.shop_price > result.max) {
result.max = curr.shop_price;
}
},
initial:{max:0}
})
类似于sql:
selectmax(shop_price) from goods group by cat_id;
</code></pre>
<h3 id="124-求平均">1.2.4 求平均</h3>
<p>求平均时主要用到了<code>finalize</code>函数</p>
<pre><code class="language-sql">db.collection.group({
key:{cat_id:1},
cond:{},
reduce:function(curr , result) {
result.cnt += 1;//统计数量
result.sum += curr.shop_price; //统计总量
},
initial:{sum:0,cnt:0},
finalize:function(result) {
result.avg = result.sum/result.cnt;//求平均
}
})
</code></pre>
<h1 id="2-aggregate">2 aggregate</h1>
<h2 id="21-定义">2.1 定义</h2>
<p><code>MongoDB</code> 中聚合(<code>aggregate</code>)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果</p>
<p>基本语法:</p>
<pre><code class="language-sql">db.collection.aggregate(docment);
</code></pre>
<p>具体方法实例有:<code>$match,$project,$group,$unwind,$sort,$limit,$skip</code>等相关操作</p>
<h2 id="22-操作aggregate">2.2 操作aggregate</h2>
<p>准备脚本</p>
<pre><code class="language-sql">> db.book.insertMany([
{"_id": 1, "author": "monday", "book": "《Java》", "like": 10},
{"_id": 2, "author": "monday", "book": "《Java Core》", "like": 20},
{"_id": 3, "author": "mengday", "book": "《Spring Boot》", "like": 15}
])
> db.book.find()
{ "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10 }
{ "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20 }
{ "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15 }
</code></pre>
<h3 id="221-match筛选">2.2.1 $match筛选</h3>
<p>用于过滤数据,只输出符合条件的文档。<code>$match</code>使用<code>MongoDB</code>的标准查询操作。<br>
筛选条件,相当于<code>SQL</code>中的<code>where</code>部分,过滤掉不满足条件的文档,可以使用常规的查询操作符,如 <code>$gt</code>、<code>$lt</code>、<code>$in</code>等。</p>
<pre><code class="language-sql">> db.book.aggregate({"$match": {"like": {"$gt": 10}}})
{ "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20 }
{ "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15 }
</code></pre>
<p>当<code>match</code>在<code>group</code>之前类似于<code>SQL</code>的<code>where</code>操作,在<code>group</code>之后类似于<code>SQL</code>的<code>having</code>操作</p>
<pre><code class="language-sql">db.book.aggregate([{"$match": {"like": {"$gte": 10}}},{$group:{_id:'$author',total:{$sum:1}}},{$match:{total:{$gte:2}}}])
{ "_id": "monday", "total": 2}
</code></pre>
<h3 id="222-project映射">2.2.2 $project映射</h3>
<p><code>$project</code>:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。<br>
映射相当于<code>SQL中的 select 映射 from </code>之间的部分,用于指定要查询的字段,或者对字段进行一些处理。<br>
投射常用的3个功能:</p>
<ul>
<li>对字段重命名。</li>
<li>在投射中使用一些表达式对字段值进行处理:数学表达式、日期表达式、字符串表达式、逻辑表达式(比较表达式、布尔表达式、控制语句)。</li>
<li>用于包含、排除字段: 设置要查询或者要过滤掉的字段,
<ul>
<li>0: 要过滤掉的字段,不显示</li>
<li>1:需要查询的字段。</li>
</ul>
</li>
</ul>
<h4 id="2221-自定义字段">2.2.2.1 自定义字段</h4>
<p>查询自定义的字段</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"_id": 0, "book": 1, "like": 1}})
{ "book" : "《Java》", "like" : 10 }
{ "book" : "《Java Core》", "like" : 20 }
{ "book" : "《Spring Boot》", "like" : 15 }
</code></pre>
<h4 id="2222-起别名">2.2.2.2 起别名</h4>
<p>对字段起别名,相当于SQL中的as的作用</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"_id": 0, "book name": "$book", "like": 1}})
{ "like" : 10, "book name" : "《Java》" }
{ "like" : 20, "book name" : "《Java Core》" }
{ "like" : 15, "book name" : "《Spring Boot》" }
</code></pre>
<h4 id="2223-引用原字段字段名">2.2.2.3 引用原字段:$字段名</h4>
<p>如果对_id进行起别名会将别名作为一个新的字段加入到文档中,而_id保持不变</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"id": "$_id", "book name": "$book", "like": 1}})
{ "_id" : 1, "like" : 10, "id" : 1, "book name" : "《Java》" }
{ "_id" : 2, "like" : 20, "id" : 2, "book name" : "《Java Core》" }
{ "_id" : 3, "like" : 15, "id" : 3, "book name" : "《Spring Boot》" }
</code></pre>
<h4 id="2224-使用算术表达式">2.2.2.4 使用算术表达式</h4>
<p>使用算术表达式 <code>$add</code>、<code>$subtract</code>、<code>$multiply</code>、<code>$divide</code>、<code>$mod</code> 处理数字类型的字段</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"like": {"$add": ["$like", "$like", 1]}}})
{ "_id" : 1, "like" : 21 }
{ "_id" : 2, "like" : 41 }
{ "_id" : 3, "like" : 31 }
</code></pre>
<h4 id="2225-字符串截取">2.2.2.5 字符串截取</h4>
<p>$substrCP: : 字符串截取操作</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"newValue": {"$substrCP": ["$book", 1, 4]}}})
{ "_id" : 1, "newValue" : "Java" }
{ "_id" : 2, "newValue" : "Java" }
{ "_id" : 3, "newValue" : "Spri" }
</code></pre>
<h4 id="2226-字符串拼接">2.2.2.6 字符串拼接</h4>
<p>$concat:: 字符串操作:将数组中的多个元素拼接在一起</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"newValue": {"$concat": ["$book", "(", "$author", ")"]}}})
{ "_id" : 1, "newValue" : "《Java》(monday)" }
{ "_id" : 2, "newValue" : "《Java Core》(monday)" }
{ "_id" : 3, "newValue" : "《Spring Boot》(mengday)" }
</code></pre>
<h4 id="2227-大小写转换">2.2.2.7 大小写转换</h4>
<p><code>$toUpper</code>: 字符串操作,转大写<br>
<code>$toLower</code>: exp, 字符串转小写</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"newValue": {"$toUpper": "$book"}}})
{ "_id" : 1, "newValue" : "《JAVA》" }
{ "_id" : 2, "newValue" : "《JAVA CORE》" }
{ "_id" : 3, "newValue" : "《SPRING BOOT》" }
</code></pre>
<h4 id="2228-日期表达式">2.2.2.8 日期表达式</h4>
<p>日期表达式:用于获取日期中的任意一部分,年月日时分秒 星期等<br>
<code>$year</code>、<code>$month</code>、<code>$dayOfMonth</code>、<code>$dayOfWeek</code>、<code>$dayOfYear</code>、<code>$hour</code>、<code>$minute</code>、<code>$second</code></p>
<ul>
<li><code>$dayOfYear</code>: 返回该日期是这一年的第几天(全年 366 天)。</li>
<li><code>$dayOfMonth</code>: 返回该日期是这一个月的第几天(1到31)。</li>
<li><code>$dayOfWeek</code>: 返回的是这个周的星期几(1:星期日,7:星期六)。</li>
<li><code>$year</code>: 返回该日期的年份部分。</li>
<li><code>$month</code>: 返回该日期的月份部分( 1 到 12)。</li>
<li><code>$week</code>: 返回该日期是所在年的第几个星期( 0 到 53)。</li>
<li><code>$hour</code>: 返回该日期的小时部分。</li>
<li><code>$minute</code>: 返回该日期的分钟部分。</li>
<li><code>$second</code>: 返回该日期的秒部分(以0到59之间的数字形式返回日期的第二部分,但可以是60来计算闰秒)。</li>
<li><code>$millisecond</code>:返回该日期的毫秒部分( 0 到 999)。</li>
<li><code>$dateToString</code>: { $dateToString: { format: , date: } }。</li>
</ul>
<p>准备脚本</p>
<pre><code class="language-sql">db.book.find()
{ "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
{ "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
{ "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
</code></pre>
<p>求日期</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"year": {"$year": "$publishDate"}}})
{ "_id" : 1, "year" : 2021 }
{ "_id" : 2, "year" : 2021 }
{ "_id" : 3, "year" : 2021 }
</code></pre>
<h4 id="2229-比较非字符串大小">2.2.2.9 比较非字符串大小</h4>
<p><code>$cmp: </code>: 用于比较两个非字符串类型的值,exp1 == exp2 返回 0, 小于返回一个负数,大于返回一个正数</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"result": {"$cmp": ["$like", 15]}}})
{ "_id" : 1, "result" : -1 }
{ "_id" : 2, "result" : 1 }
{ "_id" : 3, "result" : 0 }
</code></pre>
<h4 id="22210-比较字符串大小">2.2.2.10 比较字符串大小</h4>
<p><code>$strcasecmp</code>: 用于比较字符串, 不区分大小写,相等返回0</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"result": {"$strcasecmp": ["$author", "Monday"]}}})
{ "_id" : 1, "result" : 0 }
{ "_id" : 2, "result" : 0 }
{ "_id" : 3, "result" : -1 }
</code></pre>
<h4 id="22211-比较表达式">2.2.2.11 比较表达式</h4>
<p><code>$eq</code>: 用于判断两个表达式是否相等,相等返回true,不相等返回false, <code>区分大小写</code><br>
<code>$ne</code>: 不相等<br>
<code>$gt</code>: 大于<br>
<code>$gte</code>: 大于等于<br>
<code>$lt</code>: 小于<br>
<code>$lte</code>: 小于等于<br>
注意:<code>{"$eq": ["$author", "monday"]}</code>中<code>$eq</code>键的值是个数组而不是一个对象</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"result": {"$eq": ["$author", "monday"]}}})
{ "_id" : 1, "result" : true }
{ "_id" : 2, "result" : true }
{ "_id" : 3, "result" : false }
</code></pre>
<h4 id="22212-and连接">2.2.2.12 And连接</h4>
<p><code>$and: </code>用于连接多个条件,当所有条件为真的时候为<code>true</code></p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"result": {"$and": [{"$eq": ["$author", "monday"]}, {"$gt": ["$_id", 1]}]}}})
{ "_id" : 1, "result" : false }
{ "_id" : 2, "result" : true }
{ "_id" : 3, "result" : false }
</code></pre>
<h4 id="22213-or连接">2.2.2.13 Or连接</h4>
<p><code>$or: </code>有一个为真则为真</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"result": {"$or": [{"$eq": ["$author", "monday"]}, {"$eq": ["$_id", 1]}]}}})
{ "_id" : 1, "result" : true }
{ "_id" : 2, "result" : true }
{ "_id" : 3, "result" : false }
</code></pre>
<h4 id="22214-not连接">2.2.2.14 not连接</h4>
<p><code>$not: exp</code> 用于取反操作</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"result": {"$not": {"$eq": ["$author", "monday"]}}}})
{ "_id" : 1, "result" : false }
{ "_id" : 2, "result" : false }
{ "_id" : 3, "result" : true }
</code></pre>
<h4 id="22215-三位运算符">2.2.2.15 三位运算符</h4>
<p><code>$cond: </code>: 三位运算符</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"result": {"$cond": [ {"$eq": ["$author", "monday"]}, "M", "F" ]}}})
{ "_id" : 1, "result" : "M" }
{ "_id" : 2, "result" : "M" }
{ "_id" : 3, "result" : "F" }
</code></pre>
<h4 id="22216-ifnull连接">2.2.2.16 ifNull连接</h4>
<p><code>$ifNull: </code>: 如果字段不存在或者字段值为<code>null</code>会返回<code>replacementExpr</code>,否则返回原来的值,即字段为空时给一个默认值<br>
IFNULL(bool, default_value)</p>
<pre><code class="language-sql">db.book.aggregate({"$project": {"result": {"$ifNull": ["$price", "0.00"]}}})
{ "_id" : 1, "result" : 66 }
{ "_id" : 2, "result" : "0.00" }
{ "_id" : 3, "result" : "0.00" }
</code></pre>
<h3 id="223-group-分组">2.2.3 $group 分组</h3>
<h4 id="2231-单字段分组">2.2.3.1 单字段分组</h4>
<p>相当于SQL中的<code>group by</code> 部分<br>
<code>$group</code> 表示分组<br>
<code>_id</code>: 用于指定要分组的字段<br>
<code>count</code>: 是聚合后的结果的字段别名,类似于SQL中的as后面的别名,可以任意定义,字段前面使用<code>$</code>表示应用某个字段而不是一个普通的字符串<br>
<code>$sum</code>: 对分组中的每个文档做什么类型的聚合,是求和还是求平均数等,<code>1</code>:表示对分组的每一条文档都加1进行统计,这就相当于<code>SQL</code>中的<code>count(*)</code><br>
<code>select _id, count(*) from book group by author</code></p>
<pre><code class="language-sql">db.book.aggregate({"$group": {"_id": "$author", "count": {"$sum": 1}}})
{ "_id" : "monday", "count" : 2 }
{ "_id" : "mengday", "count" : 1 }
</code></pre>
<h4 id="2232-对多个字段进行分组">2.2.3.2 对多个字段进行分组</h4>
<p>类似于:<code>select _id, count(*) from book group by author, like</code></p>
<pre><code class="language-sql">db.book.aggregate({"$group": {"_id": {"author": "$author", "like": "$like"}, "count": {"$sum": 1}}})
{ "_id" : { "author" : "monday", "like" : 10 }, "count" : 1 }
{ "_id" : { "author" : "monday", "like" : 20 }, "count" : 1 }
{ "_id" : { "author" : "mengday", "like" : 15 }, "count" : 1 }
</code></pre>
<h4 id="2233-聚合运算">2.2.3.3 聚合运算</h4>
<p><code>$avg</code>: 求分组中某个字段的平均值<br>
<code>$max</code>: 求分组中某个字段的最大值<br>
<code>$min</code>: 求分组中某个字段最小的值<br>
<code>$first</code>: 求分组中的第一个值<br>
<code>$last</code>: 求分组中最后一个值</p>
<pre><code class="language-sql">db.book.aggregate( {"$group": {"_id": "$author", "avg": {"$avg": "$like"}}} )
{ "_id" : "monday", "avg" : 15 }
{ "_id" : "mengday", "avg" : 15 }
</code></pre>
<h4 id="2234-addtoset">2.2.3.4 addToSet</h4>
<p><code>$addToSet</code>: 字段引用, 将分组后的每个文档指定的值放在set集合中,集合不重复,无序</p>
<pre><code class="language-sql">db.book.aggregate( {"$group": {"_id": "$author", "likes": {"$addToSet": "$like"}}} )
{ "_id" : "monday", "likes" : [ 20, 10 ] }
{ "_id" : "mengday", "likes" : [ 15 ] }
</code></pre>
<h4 id="2235-push操作">2.2.3.5 push操作</h4>
<p><code>$push: exp</code>, 将分组后的每个文档指定的值放在数组中,允许重复,有序</p>
<pre><code class="language-sql">db.book.aggregate( {"$group": {"_id": "$author", "likes": {"$push": "$like"}}} )
{ "_id" : "mengday", "likes" : [ 15 ] }
{ "_id" : "monday", "likes" : [ 10, 20 ] }
</code></pre>
<h3 id="224-unwind">2.2.4 $unwind</h3>
<p>将数组的每一个元素都单独作为一条文档进行拆分。</p>
<pre><code class="language-sql"> db.comments.insert({"_id": 1, "title": "java", "comment": ["good", "very good"]})
db.comments.find()
{ "_id" : 1, "title" : "java", "comment" : [ "good", "very good" ] }
db.comments.aggregate( {"$unwind": "$comment"} )
{ "_id" : 1, "title" : "java", "comment" : "good" }
{ "_id" : 1, "title" : "java", "comment" : "very good" }
</code></pre>
<h3 id="225-sort">2.2.5 $sort</h3>
<p>对文档进行排序,相当于SQL中的order by。</p>
<pre><code class="language-sql">db.book.aggregate( {"$sort": {"_id": -1}} )
{ "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
{ "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : null }
{ "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : 66 }
</code></pre>
<h3 id="226-limit">2.2.6 $limit</h3>
<p>限制返回的条数,相当于SQL中的<code>limit count</code> 语句。</p>
<pre><code class="language-sql">db.book.aggregate( {"$limit": 2} )
{ "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : 66 }
{ "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : null }
</code></pre>
<h3 id="227-skip">2.2.7 $skip</h3>
<p>跳过前N条文档,和limit结合可用于分页。</p>
<pre><code class="language-sql">db.book.aggregate( {"$skip": 2}, {"$limit": 2} )
{ "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
</code></pre>
<h3 id="228-综合使用">2.2.8 综合使用</h3>
<p>聚合框架,就是将上一个操作符处理的结果交个下一个操作符继续处理(这就是Linux中的管道操作),可以使用任意多个操作符,同一个操作符也可以使用多次</p>
<ul>
<li>首先通过<code>$match</code>过滤掉不匹配的文档,</li>
<li>接着讲满足条件的文档交给<code>$group</code>进行分组,</li>
<li>分组后将将分组后的结果交给<code>$sort</code>进行排序,</li>
<li>然后将排序后的结果交给<code>$skip</code>处理,跳过前几条,</li>
<li>把剩下的文档交给<code>$limit</code>处理,获取最终的聚合结果。</li>
</ul>
<pre><code class="language-sql">db.book.aggregate(
{"$match": {"like": {"$gte" : 10} }},
{"$group": {"_id": "$author", "count": {"$sum": 1}}},
{"$sort": {"count": -1}},
{"$skip": 1},
{"$limit": 1}
)
{ "_id" : "mengday", "count" : 1 }
</code></pre>
<h2 id="23-常见聚合表达式">2.3 常见聚合表达式</h2>
<p>下表展示了一些聚合的计算表达式:</p>
<table>
<thead>
<tr>
<th>表达式</th>
<th>描述</th>
<th>实例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$sum</code></td>
<td>计算总和</td>
<td><code>db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}])</code></td>
</tr>
<tr>
<td><code>$avg</code></td>
<td>计算平均值</td>
<td><code>db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}])</code></td>
</tr>
<tr>
<td><code>$min</code></td>
<td>获取集合中所有文档对应值得最小值</td>
<td><code>db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])</code></td>
</tr>
<tr>
<td><code>$max</code></td>
<td>获取集合中所有文档对应值得最大值。</td>
<td><code>db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}])</code></td>
</tr>
<tr>
<td><code>$push</code></td>
<td>将值加入一个数组中,不会判断是否有重复的值</td>
<td><code>db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}])</code></td>
</tr>
<tr>
<td><code>$addToSet</code></td>
<td>将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。</td>
<td><code>db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}])</code></td>
</tr>
<tr>
<td><code>$first</code></td>
<td>根据资源文档的排序获取第一个文档数据。</td>
<td><code>db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])</code></td>
</tr>
<tr>
<td><code>$last</code></td>
<td>根据资源文档的排序获取最后一个文档数据</td>
<td><code>db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])</code></td>
</tr>
</tbody>
</table>
<h1 id="3-mapreduce">3 mapReduce</h1>
<p><code>Map-Reduce</code>是一种计算模型,简单的说就是将大批量的工作(数据)分解(<code>MAP</code>)执行,然后再将结果合并成最终结果(<code>REDUCE</code>)。<br>
<code>MongoDB</code>提供的<code>Map-Reduce</code>非常灵活,对于大规模数据分析也相当实用。</p>
<h2 id="31-mapreduce语法">3.1 mapReduce语法</h2>
<p>以下是<code>MapReduce</code>的基本语法:</p>
<pre><code class="language-sql">db.collection.mapReduce(
function() {emit(key,value);},//map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{
out: collection,
query: document,
sort: document,
limit: number
}
)
</code></pre>
<p>使用 <code>MapReduce</code> 要实现两个函数<code> Map</code> 函数和 <code>Reduce</code> 函数,<code>Map</code> 函数调用 <code>emit(key, value)</code>, 遍历 <code>collection</code> 中所有的记录, 将 key 与 value 传递给 <code>Reduce</code> 函数进行处理。<br>
<code>Map</code> 函数必须调用 <code>emit(key, value) </code>返回键值对。</p>
<p>参数说明:</p>
<ul>
<li><code>map</code> :映射函数 (生成键值对序列,作为 <code>reduce</code> 函数参数)<br>
把属于同一个组的数据,映射到一个数组上</li>
<li><code>reduce</code>:统计函数,把数组(同一组)的数据,进行运算<br>
<code>reduce</code>函数的任务就是将<code>key-values</code>变成<code>key-value</code>,也就是把<code>values数组</code>变成一个单一的值<code>value</code></li>
<li><code>out</code>:统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。</li>
<li><code>query</code>:一个筛选条件,只有满足条件的文档才会调用map函数。(query。limit,sort可以随意组合)</li>
<li><code>sort</code> 和<code>limit</code>结合的<code>sort</code>排序参数(也是在发往map函数前给文档排序),可以优化分组机制</li>
<li><code>limit</code> 发往<code>map</code>函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)</li>
</ul>
<p>以下实例在集合 orders 中查找 status:"A" 的数据,并根据 cust_id 来分组,并计算 amount 的总和。<br>
<img src="https://img-blog.csdnimg.cn/b3d0119132544b3ab853e3be4e477d26.png"></p>
<h2 id="32-与分组聚合对比">3.2 与分组聚合对比</h2>
<p>与<code>Aggregate</code>中引用集合中字段区别:</p>
<ul>
<li>在分组聚合<code>Aggregate</code>函数中,引用原字段用<code>$原字段</code></li>
<li>在<code>MapReduce</code>中引用原来字段用<code>this</code>,比如:<code>this.age</code></li>
</ul>
<h2 id="33-使用-mapreduce">3.3 使用 mapReduce</h2>
<p>准备脚本</p>
<pre><code class="language-sql">db.user.insertMany([
{"name" : "鲁迅","book" : "呐喊","price" : 38.0,"publisher" : "人民文学出版社"},
{"name" : "曹雪芹","book" : "红楼梦","price" : 22.0,"publisher" : "人民文学出版社"},
{"name" : "钱钟书","book" : "宋诗选注","price" : 99.0,"publisher" : "人民文学出版社"},
{"name" : "钱钟书","book" : "谈艺录","price" : 66.0,"publisher" : "三联书店"},
{"name" : "鲁迅","book" : "彷徨","price" : 55.0,"publisher" : "花城出版社"}
]);
</code></pre>
<p>查询每个作者的总计价格</p>
<pre><code class="language-sql">db.user.mapReduce(
function(){emit(this.name,this.price)},
function(key,value){return Array.sum(value)},
{out:"totalPrice"});
</code></pre>
<p>查看处理结果</p>
<pre><code class="language-sql">db.totalPrice.find();
{
"_id": "鲁迅",
"value": 22
}
// 2
{
"_id": "钱钟书",
"value": 93
}
// 3
{
"_id": "曹雪芹",
"value": 165
}
</code></pre>
<p>或者使用引用的方法(查询每个价格在 40以上的书,并用逗号分隔)</p>
<pre><code class="language-sql">所有函数写在一起
db.user.mapReduce(
function(){emit(this.name,this.book)},
function(key,value){return value.join(',')},
{query:{price:{$gt:40}},out:"books"})
db.books.find();
单独写,然后引用
var map=function(){emit(this.name,this.book)}
var reduce=function(key,value){return value.join(',')}
var options={query:{price:{$gt:40}},out:"books"}
db.user.mapReduce(map,reduce,options);
</code></pre>
<h2 id="34-runcommand">3.4 runCommand</h2>
<h3 id="341-语法">3.4.1 语法</h3>
<pre><code class="language-sql">db.runCommand(
{
mapReduce: <collection>,
map: <function>,
reduce: <function>,
finalize: <function>,
out: <output>,
query: <document>,
sort: <document>,
limit: <number>,
scope: <document>,
jsMode: <boolean>,
verbose: <boolean>,
bypassDocumentValidation: <boolean>,
collation: <document>
}
)
</code></pre>
<p>参数含义:</p>
<ul>
<li><code>mapReduce</code>:表示要操作的集合</li>
<li><code>map</code>:map函数</li>
<li><code>reduce</code>:reduce函数</li>
<li><code>finalize</code>:最终处理函数</li>
<li><code>out</code>:输出的集合</li>
<li><code>query</code>:对结果进行过滤</li>
<li><code>sort</code>:对结果排序</li>
<li><code>limit</code>:返回的结果数</li>
<li><code>scope</code>:设置参数值,在这里设置的值在map,reduce,finalize函数中可见</li>
<li><code>jsMode</code>:是否将地图执行的中间数据由javascript对象转换成BSON对象,替换为false</li>
<li><code>verbose</code>:是否显示详细的时间统计信息</li>
<li><code>bypassDocumentValidation</code>:是否绕过文档验证</li>
<li><code>collation</code>:其他一些校对</li>
</ul>
<h3 id="342-案例">3.4.2 案例</h3>
<p>如下操作,表示执行MapReduce操作重新统计的集合限制返回条数,限制返回条数之后再进行统计操作,如下:</p>
<pre><code class="language-sql">var map=function(){emit(this.name,this.book)}
var reduce=function(key,value){return value.join(',')}
db.runCommand({mapreduce:'user',map,reduce,out:"books",limit:4,verbose:true})
db.books.find()
执行结果:
{ "_id" : "鲁迅", "value" : "呐喊" }
{ "_id" : "曹雪芹", "value" : "红楼梦" }
{ "_id" : "钱钟书", "value" : "谈艺录,宋诗选注" }
</code></pre>
<p>这里进行对比发现,因为limit的原因,鲁迅的第一本书不见了</p>
<p><code>finalize</code>操作表示最终处理函数,如下:<code>f1</code>函数的第一个参数键表示<code>emit</code>中的第一个参数,第二个参数表示<code>reduce</code>的执行结果,我们可以在<code>f1</code>中对这个结果进行再处理</p>
<pre><code class="language-sql">var f1 = function(key,reduceValue){var obj={};obj.author=key;obj.books=reduceValue; return obj}
var map=function(){emit(this.name,this.book)}
var reduce=function(key,value){return value.join(',')}
db.runCommand({mapreduce:'user',map,reduce,out:"books",finalize:f1})
db.books.find()
执行结果:
{ "_id" : "鲁迅", "value" : { "author" : "鲁迅", "books" : "彷徨,呐喊" } }
{ "_id" : "曹雪芹", "value" : { "author" : "曹雪芹", "books" : "红楼梦" } }
{ "_id" : "钱钟书", "value" : { "author" : "钱钟书", "books" : "谈艺录,宋诗选注" } }
</code></pre><br><br>
来源:https://www.cnblogs.com/jingzh/p/16860861.html
頁:
[1]