mongodb(1)

一、数据库操作

  1. 切换数据库

    use database_name

    use school

    注:如果数据库存在,则切换到该数据库下,如果此数据库还不存在,也可以切过来,但是并不能立刻创建该数据库

  2. 查看所有得数据库

    show dbs

    备注:刚创建的数据库shcool如果不在查询的列表中,如果要显示它,则需要向school中插入数据

    db.students.insert({age:1})

  3. 查看当前使用的数据库

    db

  4. 删除数据库

    db.dropDatabase()

二、集合操作

查看集合帮助

use demo

db.demo.help()

创建集合

db.createCollection(collection_name)

创建集合并插入一个文档

db.collection_name.insert({document})

//例如

db.demo.insert({age:1})

注:上图里的ObjectId是有规律的,规律如下

之前我们使用MySQL等关系型数据库时,主键都是设置成自增的。但在分布式环境下,这种方法就不可行了,会产生冲突。为此,MongoDB采用了一个称之为ObjectId的类型来做主键。ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,依次代表:

- -
4字节: UNIX时间戳
3字节: 表示运行MongoDB的机器
2字节: 表示生成此_id的进程
3字节: 由一个随机数开始的计数器生成的值

查看当前数据库下的集合

show collections

删除当前集合

db.collection_name.drop() 例如: db.demo.drop()

文档操作

插入文档

  1. insert

db.collection_name.insert({document})

collection_name:集合的名字

document 插入的文档(数据)

注:每当插入一条新文档的时候mongodb会自动为此文档生成一个_id属性,_id属性是唯一的,用来标识一个文档,_id也可以直接指定,但如果数据库中此集合下已经有此_id的话,将插入失败2

  1. save

db.collection_name.save(document)

注:如果不指定_id字段,save方法类似于insert方法。如果指定_id字段,则会更新_id的数据。

db.demo.save({_id:1, age:2})

更新文档

语法

db.collection.update(
,
,
{
upsert: ,
multi:
}
)

参数:

query: 查询条件,指定要更新符合哪些条件的文档

update: 更新后的对象或指定一些更新的操作符

$set: 在原基础上累加

upsert: 可选,如果不存在符合条件的记录时是否插入updateObj。默认是false,不插入

multi:可选,mongodb默认只更新找到的第一条记录,如果这个参数为true,就更新所有符合条件的记录。

更新文档

db.demo.insert({name:'zhangsan'})

db.demo.update({name:'zhangsan'},{age:12})

upsert

将students集合的数据中name是lisi的值改为ww,原本数据库中没该数据,也不会插入成功,但是设置upsert为true后就能插入成功

db.demo.update({name:'lisl'}, {name:'ww'}, {upsert:true})

multi

如果有多条age是1的数据,只更新一条,如果想全部更新,需要指定{multi:true}参数

db.school.insert({age:1})
db.school.insert({age:1})
db.school.insert({age:1})
db.school.update({age:1}, {$set:{name:'lisi'}}, {multi:true})
db.school.find()

更新操作符

$set

直接指定更新后的值

use c3;
db.c3.insert({name:'a'});
db.c3.update({name:'a'},{$set:{age:10}});
db.c3.find({})

$inc

在原基础上累加

db.c3.update({name:'a'},{$inc:{age:1}})

$unset

删除指定的键

db.c3.update({name:'2'}, {$unset:{age:11}}) 删除name为'2'的文档中的age为11的数据

$push

数组中添加元素

db.c3.insert({name:'2', age:10})
db.c3.update({name:'2'}, {$push:{hobbys:'drink'}}, {multi: true})

向name为2的文档中的数组hobbys中添加drink

$ne

$ne类似于MYSQL的not in 或者 not exists

db.c3.update({hobbys:{$ne:"eating"}}, {$push:{"hobbys":"sleeping"}}, {multi:true})

将c3集合中没有hobbys属性,或者hobbys文档中hobbys中没有sleeping的集合中添加hobbys数组或者追加数据到hobbys数组

$addToSet

向集合中添加数组元素(一条)

db.c3.update({name:'a'}, {$addToSet:{"addr":"hb"}})

$each

把数组中的元素逐个添加到集合中(可以添加多条)

db.c3.update({name:'a'}, {$addToSet:{"addr":{$each:["wh", "sh"]}}})

$pop

数组中移除指定的索引中对应的元素

  • ${pop:{key:1}} 从数组末尾删除一个元素
  • ${pop:{key:-1}} 从数组开头删除一个元素

db.c3.update({name:'a'}, {$pop:{addr:1}})

修改指定索引的元素

修改c3中第一个name为a的集合中add数组文档

db.c3.update({name:'a'}, {$set:{"addr.1":"zh"}})

删除文档

remove方法是用来移除集合中的数据

语法

db.collection.remove(
   <query>,
   {
     justOne: <boolean>
   }
)

参数

- -
query (可选)删除的文档的条件。
justOne (可选)如果设为 true 或 1,则只删除匹配到的多个文档中的第一个

实例

删除wroker集合里name是a的所有文档数据

db.c3.remove({name:'a'})

即时匹配多条也只删除一条

db.c3.remove({name:'a'},{justOne:true})

查询文档

find

db.collection_name.find()

查询指定的列

语法

db.collection_name.find({querywhere},{key:1,key:1})

参数列表

- -
collection_name 集合的名字
queryWhere 参阅查询条件操作符
key 指定要返回的列
1 表示要显示

实例

只返回显示age列

db.c3.insert([{name:1,age:2},{name:1,age:3},{name:1,age:4}])

db.c3.find({}, {_id:0,age:1})

findOne

查询匹配结果的第一条数据 语法

db.collection_name.findOne()

实例

db.c3.findOne()

$in

查询字段在某个范围内

查询age为3或4的文档中的name和age
db.c3.find({age:{$in:[3,4]}}, {name:1, age:1})

$nin

查询字段不在某个范围内

db.c3.find({age:{$nin:[3,4]}}, {name:1, age:1})

$not

对特定条件取反

db.c3.find({age:{$not:{$gte:3, $lte:4}}})

释:查找age不在3到4之间的

array

where

db.c3.find({$where:"this.age>2"}, {name:1,age:1})

注意:$where后面的条件需要引号

cursor

游标不是 查询结果,而是查询的一个返回资源 或接口,通过这个接口,可以逐条读取数据

var result = db.student.find();

while(result.hasNext()){
    printjson(result.next());
}

条件操作符

条件操作符用于比较两个表达式并从mongoDB集合中获取数据

大于、大于等于、小于、小于等于

参数

- -
$gt 大于
$gte 大于等于
$lt 小于
$lte 小于等于

使用_id进行查询

语法

db.collection_name.find({"_id": ObjectId("value")})

db.c3.find({_id:ObjectId("5d99738a82b8ca8cde36ac7e")})

查询结果集的条数

语法

db.collecton_name.find().count()

db.c3.find().count()

正则匹配

语法

db.collection_name.find({key:/value/})

参数

- -
collectoin_name 集合名称
key 字段
value

查询name里包含qiao的数据,正则只能匹配字符串

db.c3.insert({name:"qiaogege"})
db.user.find({name:/qiao/})

与和或

and

find方法可以传入多个键(key),每个键(key)以逗号隔开

语法

db.collection_name.find({key1:value1, key2:value2})

查询name是1并且age是2的数据

db.c3.find({name:1,age:2})

or

语法

db.collection_name.find(
{
$or: [
{key1: value1}, {key2:value2}
]
})

查询age=2或者age=3的数据

db.c3.find({$or:[{age:2},{age:3}]})

and和or联用

语法

db.collection_name.find(
   {
     key1:value1,
     key2:value2,
     $or: [
   {key1: value1},
   {key2:value2}
     ]
   }
)

实例

查询age是2并且name是zs或者name是lisi的数据

db.c3.insert([{age:25,name:"zs"},{age:25,name:"lisi"},{age:25,name:"wangwu"}])
db.c3.find({age:25,$or:[{name:"zs"},{name:"lisi"}]})

分页查询

limit

读取指定数量的数据记录 语法

db.collection_name.find().limit(number)

var a = [];
for (var i = 0; i < 10; i++) {
	a.push({age:i})
}
db.c3.insert(a);
db.c3.find().limit(3)

skip

跳过指定数量的数据,skip方法同样接收一个数字参数作为跳过记录的条数 语法

db.collection_name.find().skip(number)

db.c3.find().skip(2)

skip+limit实现分页

通常用这种方式来实现分页功能 语法

db.collection_name.find().skip(skipNum).limit(limitNum)

db.c3.find().skip(2).limit(2)

sort排序

sort()方法可以通过参数指定排序的字段,并使用1和-1来指定排序的方式,其中1表示为升序,-1表示降序。语法

db.colleciton_name.find().sort({key:1})

db.collection_name.find().sort({key:-1})

db.c3.find().sort({age:1})

聚合查询

参考

group

group 的作用是按指定的键对集合中的文档进行分组,并执行简单的聚合函数,它与 SQL 中的 SELECT ... GROUP BY 类似。其语法格式如下:

{
  group:
   {
     ns: <namespace>,
     key: <key>,
     $reduce: <reduce function>,
     $keyf: <key function>,
     cond: <query>,
     finalize: <finalize function>
   }
}

group 支持的指令及对应描述如下:

指令 类型 描述
ns string 通过操作执行组的集合,必填。
key ducoment 要分组的字段或字段,必填。
$reduce function 在分组操作期间对文档进行聚合操作的函数。 该函数有两个参数:当前文档和该组的聚合结果文档。 必填。 类似having操作
initial document 初始化聚合结果文档, 必填。
$keyf function 替代 key。指定用于创建“密钥对象”以用作分组密钥的函数。 使用$keyf而不是 key按计算字段而不是现有文档字段进行分组。
cond document 用于确定要处理的集合中的哪些文档的选择标准。 如果省略,group 会处理集合中的所有文档。相当于where筛选
finalize function 在返回结果之前运行,此函数可以修改结果文档。
db.sales.insertMany([
{_id: 1, orderDate: ISODate("2012-07-01T04:00:00Z"), shipDate: ISODate("2012-07-02T09:00:00Z"), attr: {name: "新款椰子鞋", price: 2999, size: 42, color: "香槟金"}},
{_id: 2, orderDate: ISODate("2012-07-03T05:20:00Z"), shipDate: ISODate("2012-07-04T09:00:00Z"), attr: {name: "高邦篮球鞋", price: 1999, size: 43, color: "狮王棕"}},
{_id: 3, orderDate: ISODate("2012-07-03T05:20:10Z"), shipDate: ISODate("2012-07-04T09:00:00Z"), attr: {name: "新款椰子鞋", price: 2999, size: 42, color: "香槟金"}},
{_id: 4, orderDate: ISODate("2012-07-05T15:11:33Z"), shipDate: ISODate("2012-07-06T09:00:00Z"), attr: {name: "极速跑鞋", price: 500, size: 43, color: "西湖蓝"}},
{_id: 5, orderDate: ISODate("2012-07-05T20:22:09Z"), shipDate: ISODate("2012-07-06T09:00:00Z"), attr: {name: "新款椰子鞋", price: 2999, size: 42, color: "香槟金"}},
{_id: 6, orderDate: ISODate("2012-07-05T22:35:20Z"), shipDate: ISODate("2012-07-06T09:00:00Z"), attr: {name: "透气网跑", price: 399, size: 38, color: "玫瑰红"}}
])

假设要将集合 sales 中的文档按照 attr.name 进行分组,并限定参与分组的文档的 shipDate 大于指定时间。对应示例如下:

# select name from sales where shipDate > '2012-07-04T09:00:00Z' group by name
db.runCommand({
    group:{
      # from操作
      ns: 'sales',
      # group by操作
      key: {"attr.name": 1},
      # where操作
      cond: {shipDate: {$gt: ISODate('2012-07-04T09:00:00Z')}},
      # 这个处理类似having的操作; curr:是当前遍历的文档,result聚合的结果文档
      $reduce: function(curr, result){
      },
      initial: {}
    }
})
命令执行后,会返回一个结果档。其中, retval 包含指定字段 attr.name 的数据,count 为参与分组的文档数量,keys 代表组的数量,ok 代表文档状态

# select shipDate, count(price) total from sales where shipDate > '2012-07-04T00:00:00Z' group by shipDate
db.runCommand({
    group:{
        ns: 'sales',
        key: {shipDate: 1},
        cond: {shipDate: {$gt: ISODate('2012-07-04T00:00:00Z')}},
        initial: {total: 0},
        $reduce: function(curr, result){
        	result.total += curr.attr.price;
        }
    }
})
# select shipDate, count, total, avg(price) avg from sales where shipDate > '2012-07-04T00:00:00Z' group by shipDate
db.runCommand(
{
    group:{
        ns: 'sales',
        key: {shipDate: 1},
        cond: {shipDate: {$gt: ISODate('2012-07-04T00:00:00Z')}},
        $reduce: function(curr, result){
        	result.total += curr.attr.price;
        	result.count ++;
        },
        initial: {total: 0, count: 0},
        finalize: function(result) {
        	result.avg = Math.round(result.total / result.count);
        }
     }
})
上面的示例中改动了 $reduce 函数,目的是为了统计 count。然后新增了 finalize,目的是计算分组中的平均销售额

aggregate聚集框架

管道操作符

常用管道 含义
$group 将collection中的document分组,可用于统计结果
$match 过滤数据,只输出符合结果的文档
$project 修改输入文档的结构(例如重命名,增加、删除字段,创建结算结果等)
$sort 将结果进行排序后输出
$limit 限制管道输出的结果个数
$skip 跳过制定数量的结果,并且返回剩下的结果
$unwind 将数组类型的字段进行拆分

管道的概念:

管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。

MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。

这里我们介绍一下聚合框架中常用的几个操作:

  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
  • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
  • $limit:用来限制MongoDB聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • $group:将集合中的文档分组,可用于统计结果。
  • $sort:将输入文档排序后输出。
  • $geoNear:输出接近某一地理位置的有序文档。

聚合的表达式 :

表达式 描述 实例
$sum 计算总和。 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}])
$avg 计算平均值 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}])
$min 获取集合中所有文档对应值得最小值。 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])
$max 获取集合中所有文档对应值得最大值。 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}])
$push 在结果文档中插入值到一个数组中。 db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}])
$addToSet 在结果文档中插入值到一个数组中,但不创建副本。 db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}])
$first 根据资源文档的排序获取第一个文档数据。 db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])
$last 根据资源文档的排序获取最后一个文档数据 db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])

数据准备

use shcool
db.createCollection("student")
var arr = [
	{_id:1, name:'zs',subject:'语文',score:90,cid:1},
	{_id:2, name:'zs',subject:'数学',score:95,cid:1},
	{_id:3, name:'zs',subject:'英语',score:95,cid:1},
	{_id:4, name:'ls',subject:'语文',score:80,cid:1},
	{_id:5, name:'ls',subject:'数学',score:85,cid:1},
	{_id:6, name:'ls',subject:'英语',score:90,cid:1},
	{_id:7, name:'ww',subject:'语文',score:70,cid:2},
	{_id:8, name:'ww',subject:'数学',score:75,cid:2},
	{_id:9, name:'ww',subject:'英语',score:55,cid:2}
]
db.student.save(arr)

db.createCollection("class")
db.class.save([{_id:1, cid: 1, name:"3年1班"}, {_id:2, cid: 2, name:"3年2班"}])

操作

# 查询name为ls的
db.student.aggregate([
	{$match: {name:'ls'}}
])

# 查询数学成绩大于等于80,小于90的人
db.student.aggregate([
	{$match: {subject : "数学", score: {$gte: 80, $lt: 90}}}
])

# 根据name分组求每个人的选课数
db.student.aggregate([
	{$group:{_id: "$name", snumber: {$sum:1}}}
])

#_id: 指定分组的字段
# "$name": 引用集合中的文档"name", 需要用$符号
# snumber是{$sum:1}计算出来的值的别名
# {$sum:1}这里是1, 和mysql中count(1)一个意思

# 求每个人的总分
db.student.aggregate([
	{$group:{_id: "$name", snumber: {$sum:"$score"}}}
])
# $sum:"$score" 在这里的意思是sum求和

# 成绩大于等于80的科目求总分,同时总分要大于240分
db.student.aggregate([
	{$match: {score: {$gte: 80}}},
	{$group:{_id: "$name", snumber: {$sum: "$score"}}},
	{$match: {snumber: {$gte:240}}}
])
# $match在group上面前面类似于mysql的where
# $match 在 group后面,类似于mysql的having

# 按名字分组求平均
db.student.aggregate([
	{$group:{_id: "$name", snumber: {$avg : "$score"}}}
])

聚合管道操作符

looup

$lookup 的作用是对同一数据库中的集合执行左外连接,其语法格式如下:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}
领域 描述
from 指定集合名称。
localField 指定输入 $lookup 中的字段。
foreignField 指定from 给定的集合中的文档字段。
as 指定要添加到输入文档的新数组字段的名称。 新数组字段包含from集合中的匹配文档。 如果输入文档中已存在指定的名称,则会覆盖现有字段 。
# select c.*, studentArr from class c inner join student s where c.cid = s.cid
# 不同的是这个studentArr是以数组的形式作为class的字段出现
db.class.aggregate([
	{
		$lookup: {
			from: "student",
			localField: "cid", #class中的字段
			foreignField: "cid", #student中的字段
			as: "studentArr"
		}
	}
]).pretty()
# 结果部分截取
{
	"_id" : 1,
	"cid" : 1,
	"name" : "3年1班",
	"studentArr" : [
		{
			"_id" : 1,
			"name" : "zs",
			"subject" : "语文",
			"score" : 90,
			"cid" : 1
		},
		{
			"_id" : 2,
			"name" : "zs",
			"subject" : "数学",
			"score" : 95,
			"cid" : 1
		}
}
unwind

unwind 能将包含数组的文档拆分称多个文档,其语法格式如下:

{
  $unwind:
    {
      path: <field path>,
      includeArrayIndex: <string>,
      preserveNullAndEmptyArrays: <boolean>
    }
}
指令 类型 描述
path string 指定数组字段的字段路径, 必填。
includeArrayIndex string 用于保存元素的数组索引的新字段的名称。
preserveNullAndEmptyArrays boolean 默认情况下,如果pathnull、缺少该字段或空数组, 则不输出文档。反之,将其设为 true 则会输出文档。
db.shoes.save({_id: 1, brand: "Nick", sizes: [37, 38, 39]})

> db.shoes.aggregate([{$unwind : "$sizes"}])
{ "_id" : 1, "brand" : "Nick", "sizes" : 37 }
{ "_id" : 1, "brand" : "Nick", "sizes" : 38 }
{ "_id" : 1, "brand" : "Nick", "sizes" : 39 }

显然,这样的文档更方便我们做数据处理。preserveNullAndEmptyArrays 指令默认为 false,也就是说文档中指定的 path 为空、null 或缺少该 path 的时候,会忽略掉该文档。假设数据如下:

> db.shoes2.insertMany([
	{"_id": 1, "item": "ABC", "sizes": ["S", "M", "L"]},
	{"_id": 2, "item": "EFG", "sizes": [ ]},
	{"_id": 3, "item": "IJK", "sizes": "M"},
	{"_id": 4, "item": "LMN" },
	{"_id": 5, "item": "XYZ", "sizes": null}
])
> db.shoes2.aggregate([{$unwind: "$sizes"}])
{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }
# 已经忽略了 sizes == null, sizes.leng <= 0, size == undefind的数据

_id245 的文档由于满足 preserveNullAndEmptyArrays 的条件,所以不会被拆分。

以上就是 unwind 的基本用法和作用介绍,更多与 unwind 相关的知识可查阅官方文档 unwind

out

out 的作用是聚合 Pipeline 返回的结果文档,并将其写入指定的集合。要注意的是,out 操作必须出现在 Pipeline 的最后。out 语法格式如下

{ $out: "" }

> db.student.aggregate([
	{$group:{_id: "$name", snumber: {$avg : "$score"}}},
	{ $out : "avg_result" }
])
> show tables
avg_result
# 发现生成了一个集合 avg_result

Map-Reduce

Map-reduce 用于将大量数据压缩为有用的聚合结果,其语法格式如下:

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>,
    writeConcern: <document>
})

其中,db.runCommand({mapReduce: }) 也可以写成 db.collection.mapReduce()。各指令的对应描述如下:

指令 类型 描述
mapReduce collection 集合名称,必填。
map function JavaScript 函数,必填。
reduce function JavaScript 函数,必填。
out string or document 指定输出结果,必填。
query document 查询条件语句。
sort document 对文档进行排序。
limit number 指定输入到 map 中的最大文档数量。
finalize function 修改 reduce 的输出。
scope document 指定全局变量。
jsMode boolean 是否在执行mapreduce 函数之间将中间数据转换为 BSON 格式,默认 false
verbose boolean 结果中是否包含 timing 信息,默认 false
bypassDocumentValidation boolean 是否允许 mapReduce在操作期间绕过文档验证,默认 false
collation document 指定要用于操作的排序规则
writeConcern document 指定写入级别,不填写则使用默认级别。
简单的 mapReduce

一个简单的 mapReduce 语法示例如下:

var mapFunction = function() { ... };
var reduceFunction = function(key, values) { ... };
db.runCommand(
{
 mapReduce: <input-collection>,
 map: mapFunction,
 reduce: reduceFunction,
 out: { merge: <output-collection> },
 query: <query>
})

map 函数负责将每个输入的文档转换为零个或多个文档。map 结构如下:

function() {
   ...
   emit(key, value);
}

emit 函数的作用是分组,它接收两个参数:

  • key: 指定用于分组的字段
  • value : 要聚合的字段

map 中可以使用 this 关键字引用当前文档。

reduce 结构如下:

function(key, values) {
   ...
   return result;
}

reduce 执行具体的数据处理操作,它接收两个参数:

  • key:与 map 中的 key 相同,即分组字段。
  • values:根据分组字段,将相同 key 的值放到同一个数组,values 就是包含这些分类数组的对象。

out 用于指定结果输出,out: 会将结果输出到新的集合,或者使用以下语法将结果输出到已存在的集合中:

out: { <action>: <collectionName>
        [, db: <dbName>]
        [, sharded: <boolean> ]
        [, nonAtomic: <boolean> ] }

要注意的是,如果 out 指定的 collection 已存在,那么它就会覆盖该集合。在开始学习之前,我们需要准备以下数据:

db.mprds.insertMany([
    {_id: 1, numb: 3, score: 9, team: "B"},
    {_id: 2, numb: 6, score: 9, team: "A"},
    {_id: 3, numb: 24, score: 9, team: "A"},
    {_id: 4, numb: 6, score: 8, team: "A"}
])

# emit 指定按numb分组,聚合字段为score
var func_map = function() {
	emit(this.team, this.score);
}
var func_reduce = function(key, values) {
	return Array.sum(values);
}
# 按numb分组查询 team="A" 每个人的总分
db.student.mapReduce(func_map, func_reduce, {query: {team: "A"}, out: "mprds_result"})

接着定义 map 函数、reduce 函数,并将其应用到集合 mrexample 上。然后为输出结果指定存放位置,这里将输出结果存放在名为 mrexample_result 的集合中。

finallize(having) 剪枝

finallize 用于修改 reduce 的输出结果,其语法格式如下:

function(key, reducedValue) {
   ...
   return modifiedObject;
}

它接收两个参数 :

key,与 map 中的 key 相同,即分组字段。

reduceValue, 一个Ojbect,是reduce的输出

上面我们介绍了 mapreduce,并通过一个简单的示例了解 mapReduce 的基本组成和用法。实际上我们还可以编写功能更丰富的 reduce 函数,甚至使用 finallize 修改 reduce 的输出结果。以下 reduce 函数将传入的 values 进行计算和重组,返回一个 reduceVal 对象:

var func_reduce2 = function(key, values){
	reduceVal = {team: key, score: values, total: Array.sum(values), count: values.length};
	return reduceVal;
};

reduceVal 对象中包含 teamscoretotalcount 四个属性。但我们还想为其添加 avg属性,那么可以在 finallize 函数中执行 avg 值的计算和 avg 属性的添加工作:

var func_finalize = function(key, values){
	values.avg = values.total / values.count;
	return values;
};

map 保持不变,将这几个函数作用于集合 mprds 上,对应示例如下:

# emit 指定按numb分组,聚合字段为score
var func_map = function() {
	emit(this.team, this.score);
}
var func_reduce2 = function(key, values){
	reduceVal = {team: key, score: values, total: Array.sum(values), count: values.length};
	return reduceVal;
}
# 这个key和上面是同一个(分组的字段), values是func_reduce2函数的返回结果
var func_finalize = function(key, values) {
	values.avg = values.total / values.count;
	return values;
}
db.mprds.mapReduce(func_map, func_reduce2, {query: {team: "A"}, out: "mprds_result", finalize: func_finalize})

> db.mprds_result.find()
{ "_id" : "A", "value" : { "team" : "A", "score" : [ 9, 9, 8 ], "total" : 26, "count" : 3, "avg" : 8.666666666666666 } }

finallize 在 后面使用,微调 的处理结果。这着看起来像是一个园丁在修剪花圃的枝丫,所以人们将 形象地称为“剪枝”。

要注意的是:map 会将 key 值相同的文档中的 value 归纳到同一个对象中,这个对象会经过 reducefinallize。对于 key 值唯一的那些文档,指定的 keyvalue 会被直接输出。

简单的聚合

count

count 用于计算集合或视图中的文档数,返回一个包含计数结果和状态的文档。其语法格式如下

{
  count: <collection or view>,
  query: <document>,
  limit: <integer>,
  skip: <integer>,
  hint: <hint>,
  readConcern: <document>
}

count 支持的指令及对应描述如下:

指令 类型 描述
count string 要计数的集合或视图的名称,必填。
query document 查询条件语句。
limit integer 指定要返回的最大匹配文档数。
skip integer 指定返回结果之前要跳过的匹配文档数。
hint string or document 指定要使用的索引,将索引名称指定为字符串或索引规范文档。

假设要统计集合 mprds 中的文档数量,对应示例如下:

db.runCommand({count: 'mprds'})
{ "n" : 4, "ok" : 1 }

假设要统计集合 mprdsnumb6 的文档数量,对应示例如下:

> db.runCommand({count: 'mprds', query: {numb: {$eq: 6}}})
{ "n" : 2, "ok" : 1 }

指定返回结果之前跳过 1 个文档,对应示例如下:

> db.runCommand({count: 'mprds', query: {numb: {$eq: 6}}, skip: 1})
{ "n" : 1, "ok" : 1 }

更多关于 count 的知识可查阅官方文档 Count

distinct

distinct 的作用是查找单个集合中指定字段的不同值,其语法格式如下:

{
  distinct: "<collection>",
  key: "<field>",
  query: <query>,
  readConcern: <read concern document>,
  collation: <collation document>
}
指令 类型 描述
distinct string 集合名称, 必填。
key string 指定的字段, 必填。
query document 查询条件语句。
readConcern document
collation document

准备以下数据 :

db.createCollection("dress")1
db.dress.insertMany([
	{_id: 1, "dept": "A", attr: {"款式": "立领", color: "red" }, sizes: ["S", "M" ]},
	{_id: 2, "dept": "A", attr: {"款式": "圆领", color: "blue" }, sizes: ["M", "L" ]},
	{_id: 3, "dept": "B", attr: {"款式": "圆领", color: "blue" }, sizes: "S" },
	{_id: 4, "dept": "A", attr: {"款式": "V领", color: "black" }, sizes: ["S" ] }
])
# 计集合 dress 中所有文档的 dept 字段的不同值
# select distinct dept from dress
db.runCommand ( { distinct: "dress", key: "dept" } )
{ "values" : [ "A", "B" ], "ok" : 1 }

# select distinct attr.款式 from dress
db.runCommand ( { distinct: "dress", key: "attr.款式" } )
{ "values" : [ "立领", "圆领", "V领" ], "ok" : 1 }

#就算值是数组, distinct 也能作出正确处理
db.runCommand ( { distinct: "dress", key: "sizes" } )
{ "values" : [ "M", "S", "L" ], "ok" : 1 }

参考

mongodb速成(1)

聚合操作参考

posted @ 2019-10-14 23:31  复合式→展开式  阅读(1038)  评论(0编辑  收藏  举报