even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、MongoDB安装

建议安装 mongodb5.0,这样就不需要太多的手动配置,直接安装

配置环境变量,把安装目录下的bin添加到环境变量中

net start MongoDB  // 启动服务
net stop MongoDB  // 停止服务

注意:如果提示拒绝服务,那么cmd需要以管理的身份进行启动,mongodb的服务端口是27017

mongod --verson    //查看版本
mongo              // 启动mongo命令行管理界面

2、MongoDB的curd操作

创建数据库

use  DATABASE_NAME

如果数据库不存在,则创建数据库,否则切换到指定数据库

show dbs           //查看所有数据库,不包含空数据库
db                //显示当前数据库
show collections  //查看所有集合

 在数据库中创建集合

db.createCollection(name, options)

 这里的name表示集合的名称, options表示配置

创建集合

db.createCollection('user')  // 创建user的集合

在mongodb不需要去创建集合,当插入数据的时候会自动创建

 插入数据

注意:在插入数据的时候,_id表示主键id,如果指定了则以指定的写入,如果不指定,系统会生成一个唯一的主键id

db.COLLECTION_NAME.insertOne({...data})  //插入一条数据

 执行插入成功后返回, 相当于是上一语句的返回值

{
        "acknowledged" : true,
        "insertedId" : ObjectId("60fa07d62c50f9abe5c866c8")  //自动生成的id
}

插入多条数据

db.COLLECTION_NAME.insertMany(
    [...data], 
    {
         ordered: <boolean>  // 表示是否以乱序写入,默认是true
    }
)

注意:如果将ordered参数设置为false,mongoDB可以打乱文档的写入的顺序,以便优化写入操作的性能, 在以顺序写入时一旦遇到错误,操作就会退出, 余下的文档无论正确与否都不会被写入, 如果以乱序写入文档,即使某些文档造成了错误,余下的正确文档仍然会被写入

创建多个文档或单个文档

db.COLLECTION_NAME.insert(
    data | [...data],
    {
         ordered: <boolean>
    }
)

注意:这条语句执行成功后,返回的是插入成功的条数,但是如果执行失败,那么将和insertMany的失败结果是一样的

insertOne, insertMany与Insert的区别

  • 三个命令的返回的结果文件格式不一样
  • insertOne与insertMany不支持db.collection.explain()命令,而insert支持db.collection.explain()命令

 当用db.COLLECTION_NAME.save()处理一个文档的时候,它会调用insert命令,所以返回的结果与Insert一致

 

ObjectId函数

通常来讲,可以使用ObjectId()来获得一个新的id,也可以使用该方法得到相应的创建时间

ObjectId()    --得到一个新的id
ObjectId('6101d4e05a1ac41c41809c73')  --得到以指定id的ObjectId
ObjectId('6101d4e05a1ac41c41809c73').getTimestamp()  --获得创建的时间

 

读取数据

命令

db.COLLECTION_NAME.find(query, projection)
-- query 文档定义了读取操作时筛选文档的条件
-- projection 文档定义了对读取结果进行的投射即需要输出的字段

读取全部文档, 即不筛选也不投射

db.COLLECTION_NAME.find()  加上.pretty()可以使文档在命令行中显示的更清楚

匹配查询 

db.user.find({name: 'abc'})   --表示查询名字为abc的数据
db.user.find({age: {$gt: 15}})  --表示查询年龄大于15的所有数据
db.user.find({name: {$lt: 'f'}})  --表示查询的姓名首字母在f之前的所有数据

 

 

 比较操作符

$in   匹配字符值与任一查询值相等的文档

$nin  匹配字符值与任一查询值都不相等的文档

db.user.find({name: {$in: ['aaa', 'bbb']}})  --表示读取名字是其中一项的所有组合
db.user.find({name: {$nin:['aaa', 'bbb']}}) --表示读取的名字不是其中一项的所有组合

逻辑操作符

$not   匹配筛选条件不成立的文档

$and  匹配多个筛选条件全部成立的文档

$or     匹配至少一个筛选条件成立的文档

$nor   匹配多个筛选条件全部不成立的文档

db.user.find({age: {$not: {$gt:12}}})  --表示筛选的数据的年龄小于等于12
db.user.find({$and: [{name: {$eq: 'aaa'}}, {age: {$gt: 5}}]})  --查找所有名字为aaa并且年龄大于5的所有数据
db.user.find({name: 'aaa', age: {$gt: 5}})  --与上面的效果一致
db.user.find({age: {$gt: 2, $lt: 30}}) --表示查找年龄大于2小于30的所有数据

db.user.find({$or: [{name: 'aaa'}, {name: 'bbb'}]})  --查找所有名字等于aaa或bbb的数据
db.user.find({name: {$in: ['aaa', 'bbb']}})  --与上面效果一样,但是命令简洁

db.user.find({$nor: [{name: 'aaa'}, {name: 'bbb'}]})  --表示查询名字即不等于aaa也不等于bbb的所有数据

字段操作符

$exists  匹配包含查询字段的文档, 值是true或false

$type  匹配字段类型符合查询值的文档

db.user.find({'age': {$exists: true}})  --表示读取所有含有age字段的所有数据
db.user.find({_id: {$type: “string”}})  --表示读取文档主键是字符串的所有值
db.user.find({_id: {$type: [“string”, “number”]}}) --表示读取文档主键是字符串或数据值
db.user.find({_id: {$type: ["objectId",  "object"]}}) --表示读取文档主键是ObjectId或都是复合主键的情况
db.user.find({name: {$type: “null”}})  --找到所有name的值是null的文档

数组操作符

$all  匹配数组字段中包含所有查询值的文档

$elemMatch  匹配数组字段中至少存在一个值满足筛选条件的文档

--数据:{_id: ***, name: 'aaa', age: '12', addr: ["fujian", 'jiangxi']}

db.user.find('addr': {$all: ['fujian', 'jiangxi']})  --表示匹配到addr这个字段数组里的值,不论顺序,全部匹配即可

db.user.find({addr: {$elemMatch: {$eq: "fujian"}}})  --表示在addr数组字段中包含了fujian这个条件和筛选

db.user.find({addr: {$all: [{$elemMatch: {$eq:'fujian'}}, {$elemMatch: {$eq:'jiangxi'}}]}})  --表示在addr这个数组字段中全部满足匹配指定值

 正则操作符

$regex  匹配满足正则表达式的文档

语法

{<field>: { : /pattern/, : '<options>' }}
{<field>: { : /pattern/<options> }}

使用

--在和$in操作符一起使用时,只能使用/prattern/<options>的方式
db.user.find({name: {$in: [/^a/, /^b/]}})  --表示查询name以a或者以b开头的所有字段
db.user.find({name: {$in: [/i/i, /A/i]}})  --表示查询name中包含i与A的所有数据,不区分大小写

--平时推荐使用{<field>: {: /pattern/, : '<options>'}}
db.user.find({name: {$regex: /A/, $options: 'i'}}) --表示查询姓名里包含A的所有数据,不区分大小写

 文档游标操作

db.COLLECTION_NAME.find()返回的是一个文档集合游标,  在不迭代游标的情况下, 只列出前20个文档

var mycursor = db.user.find()  --可以获得文档游标

注意: 当遍历完游标中所有的文档后,或者在10分钟后,游标就会自动关闭

可以使用noCursorTimeout()函数来取消10分钟后游标自动关闭的操作

var mycursor = db.user.find().noCursorTimeout()   --取消游标自动关闭

mycursor.close()  --手动关闭游标

在这之后, 在不遍历游标的情况下,需要手动进行关闭,但是如果遍历完游标,游标也会自动关闭

游标函数

mycursor.hasNext()                --判断是否有下一个
mycursor.next()                   --取出下一个数据

while(mycursor.hasNext()) {
    printjson(mycursor.next())
}

mycursor.forEach()
mycursor.forEach(printjson)       --打印出所有的文档

mycursor.limit()                  --相同于mysql里的limit表示返回指定数量的文档
mycursor.skip()                   --相当于mysql里的offset表示跳过指定数据的文档
mycursor.skip(2).limit(2)         --表示从第2条开始打印,打印出两条数据

mycursor.count(<applySkipLimit>) 
--默认情况下, <applySkipLimit>为false, 即mycursor.count()不会考虑mycursor.skip()和mycursor.limit()的效果, 但是如果传入true那么就会受影响
--在数据库分布式结构比较复杂时,元数据中的文档数量可能不准确,在这种情况下,应该避免应用不提供筛选条件的mycursor.count()函数,而使用聚合管道来计算文档数量

mycursor.sort(<document>)
--这里的document定义了排序要求
--1表示由小到大的排列,-1表示由大到小的排列
例如
mycursor.sort({age: -1, score: 1})  --表示年龄小大到小,成绩由小到大的排列

注意:执行顺序 sort > skip > limit

文档投影

db.COLLECTION_NAME.find(<query>, <projection>) --不使用投影的时候,返回的是筛选条件的完整字段文档
projection语法  {field:inclusion}  --1表示返回字段 0表示不返回字段

db.user.find({}, {name: 1, _id: 0})  --那么就只返回name字段,默认文档_id是会被返回的

注意:除了文档主键之外, 我们不可以在投影文档中混合使用包含和不包含这两种投影操作,即要么就列出所有想要的字段,要么就列出所有不想要的字段

db.user.find({}, {name: 1, _id: 0})  --可以被允许,因为_id是文档主键
db.user.find({}, {name: 1, age:0}) --不可以执行,因为两者都不是文档主键,并且同时存在
db.user.find({}, {name: 0})  --表示打印除了name字段以外的所有字段

--对数组进行文档投影
db.user.find({}, {name:1, _id:0, addr:{$elemMatch:{$eq: 'fujian'}}})  --表示如果addr中含有fujian的值,那么就显示,否则这个字段不显示

更新文档

db.COLLECTION_NAME.update()

db.COLLECTION_NAME.update(<query>, <update>, <options>)
--<query>文档定义了更新操作时筛选文档的条件, 与find里的筛选条件相同
--<update>文档提供了更新的内容
--<options>文档声明了一些更新操作的参数  {multi: <boolean>}表示是否更新多个文档即把所有匹配到的内容进行全量更新 {upsert: <boolean>}决定是去更新还是创建记录,配置该字段后,如果
没有匹配到文件就会创建一个对应文档 {arrayFilters: [...]}表示操作数组的过滤
--_id字段是不能被改变的
db.user.update({name: 'even'}, {name: 'even', age: 32})  --注意更新的时候需要把没有变化的字段写上去,否则系统会认为是删除字段的操作, 如果原来没有的字段,那么就视为添加字段
--默认只更新匹配到的第一条数据

文档更新操作符

$set  更新或新增字段(不会删除字段)

db.user.update({name: 'even'}, {$set: {sex: "man"}})  --会添加sex字段,但是不会删除没有写入的原有字段
db.user.update({name: 'even'}, {$set: {'info.fav': 'football'}})  --前提是info是个对象,那么更新对象用info.fav,需要加引号

--更新数组
db.user.update({name: 'bill'}, {$set: {'addr.1': "beijing"}})  --这里的’addr.1‘是表示原有数组的第一项, 如果是添加那么则可以把下标往后添加

$unset  删除字段

db.user.update({name: 'even'}, {$unset: {'like.test': ''}})  --会删除like对象下的test字段
db.user.update({name: 'even'}, {$unset: {'addr.0': ''}})  --会把add数组中的第一项删除,但是这里的删除是指把值设为null而不改变长度

$rename  重命名字段  --如果rename的字段不存在,那么文档将不会被改变, 如果新的字段在原来文档中就有,那么系统会先删除原有字段,然后再赋值新的字段

--可以利用$rename更把对象中的值放到外面一层,或者把外面一层的数据放到内嵌的对象中

 db.user.update({name: 'even'}, {$rename: {info: "like.info"}})  --把外面的info字段放到like对象中

--注意:类似的操作对数组不能操作

$inc   加减字段值

$mul  相乘字段值

db.user.update({name: 'aaa'},{$inc: {age: 1}})   --age在原有的基础上加1,如果是负数,那么就是减1
db.user.update({name: 'aaa'},{$mul: {age: 2}})  --age在原有的基础出乘于2,如果是小数,那么就是除于

注意:以上方法只能使用在数值上,如果操作的是不存在的字段,那么会创建对应的字段,并且赋值为0

$min   比较减小字段值

$max  比较增大字段值

db.user.update({name: 'aaa'}, {$min: {age: 13}})  --表示找到指定的记录,比对age的值,取原本的值与当前的13进行比较,把值赋值为较小的值,如果原本的值比较小,那么数据不改变
db.user.update({name: 'aaa'}, {$max: {age: 33}})  --表示找到指定的记录,比对age的值,取原本的值与当前的33进行比较,把值赋值为较大的值,如果原本的值比较大,那么数据不改变

注意:如果比较不存在的值 ,那么系统默认会添加字段,并把值赋值给新添加的字段,如果比较的数据类型不一致,那么会比较BSON数据类型的排序规则进行比较

数组更新操作符

$addToSet  向数组增添元素

db.user.update({name: 'yes'}, {$addToSet: {fav: {$each: ['football', 'computer']}}})  
--表示把【football, computer】中的每一项添加到fav中,而不是把总的数组添加到fav字段中
--$addToSet是表示字符串,布尔值,数值型添加不重复的值,如果重复的值则不添加,如果是数组或object那么要顺序值全部相等时视为相等

$pop  从数组删除元素

db.user.update({name: 'yes'}, {$pop: {fav: 1}})  --表示删除fav数组中的最后一项
db.user.update({name: 'yes'}, {$pop: {fav: -1}}) --表示删除fav数组中的第一项
db.user.update({name: 'yes'}, {$pop: {fav.3: -1}}) --表示删除fav数组中第四个数组的第一项

$pull  从数组中有选择性的移除元素

db.user.update({name: 'yes'}, {$pull: {fav: {$regex: /ea/, $options: 'i'}}})  --表示删除掉fav数组中包含有ea字符的所有项
db.user.update({name: 'yes'}, {$pull: {fav: {$elemMatch: {$regex: /ea/, $options: 'i'}}}})  --表示删除fav数组中所有含有ea字符串的子数组

$pullAll  从数组中有选择性的移除元素  相当于{$put: {<field>: $in: [<value1>, <value2>] }}}

 语法: { $pullAll: {<field>: [<value1>, <value2>] }}}

$push  向数组中增添元素

向数组中添加元素,$push与$addToSet命令相似,但是$push命令的功能更强大,和$addToSet命令一样,如果$push中指定的数组字段不存在,这个字段会被添加到原文档中, 也可以与$each搭配使用

db.user.update({name: 'yes'}, {$push: {fav: {$each: ['eee', 'fff']}}})  --表示把【eee, fff】插入到fav数组的最后
db.user.update({name: 'yes'}, {$push: {fav: {$each: ['eee', 'fff'], $position: 0}}})  --表示把【eee, fff】插入到fav数组的第一位

$sort  对数据中的元素进行排序需要与$push, $each搭配使用

db.user.update({name: 'yes'}, {$push: {fav: {$each: ['eee', 'fff'], $sort: 1}}})  --表示把【eee, fff】插入到fav数组,并且进行由小到大的排序

$slice 从数组截取指定长度的数组,并且赋值给数组

数组中修改数据的操作

db.COLLECTION_NAME.update({<array>: <query selector>}, {<update operator>: {"array.$": value}})
--更新数组中的特定元素
--$是数组中第一个符合筛选条件的数组元素的点位符
> db.user.find({name:"yes"})

--{ "_id" : ObjectId("6105d6fe38ed539a9de23601"), "name" : "yes", "age" : 12, "fav" : [ "fff", "aaa", "bbb", "ccc", "ddd", "fff" ] }
> db.user.update({name: 'yes', fav: 'fff'}, {$set: {"fav.$": 'haha'}})
--WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find({name:"yes"})
--{ "_id" : ObjectId("6105d6fe38ed539a9de23601"), "name" : "yes", "age" : 12, "fav" : [ "haha", "aaa", "bbb", "ccc", "ddd", "fff" ] }

> db.user.find()
--{ "_id" : ObjectId("6113031aa9a15f04e8fac55c"), "name" : "even", "json" : [ { "name" : "aaa", "age" : 1 }, { "name" : "bbb", "age" : 2 }, { "name" : "ccc", "age" : 3 } ] }
> db.user.update({name:"even", "json.name": "bbb"}, {$set: {"json.$.age":18}})
--WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find()
--{ "_id" : ObjectId("6113031aa9a15f04e8fac55c"), "name" : "even", "json" : [ { "name" : "aaa", "age" : 1 }, { "name" : "bbb", "age" : 18 }, { "name" : "ccc", "age" : 3 } ] }

注意:只是更改符合条件的第一个元素

db.user.find()
--{ "_id" : ObjectId("6111ab9f4ddd3fd50dd2c37a"), "name" : "test", "age" : 12, "fav" : [ "bbb", "ccc", "ddd", "fff", [ "111", "222" ] ] }
db.user.update({name: "test"}, {$set: {"fav.$[]": 'test'}})
--WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
db.user.find()
--{ "_id" : ObjectId("6111ab9f4ddd3fd50dd2c37a"), "name" : "test", "age" : 12, "fav" : [ "test", "test", "test", "test", "test" ] }

注意:以上代码会把所以值都替换成指定的值

db.COLLECTION_NAME.save(<document>): 如果document文档中包含了_id字段, save()命令将会调用db.COLLECTION_NAME.update()命令{upsert: true}

 如果需要更改数组中的多个值, 可以使用arrayFilters进行修改

> db.user.find({name: "even"})
--{ "_id" : ObjectId("6113031aa9a15f04e8fac55c"), "name" : "even", "json" : [ { "name" : "aaa", "age" : 3 }, { "name" : "aaa", "age" : 3 }] }
> db.user.update({name:"even"}, {$set: {"json.$[elem].age": 12}}, {arrayFilters: [{"elem.name": "aaa"}]})
--WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find({name: "even"})
--{ "_id" : ObjectId("6113031aa9a15f04e8fac55c"), "name" : "even", "json" : [{ "name" : "aaa", "age" : 12 }, { "name" : "aaa", "age" : 12 }]}
>

注意:这里的elem是指arrayFilters匹配到的元素, 具体可以参看官网 https://docs.mongodb.com/manual/reference/method/db.collection.update/#std-label-update-arrayFilters

删除文档

使用语句

db.collection.remove(<query>,<justOne>)  --默认是删除所有满足条件的所有文档,如果只需要删除满足条件的第一篇文档,那么justOne可以设置为true
db.collection.remove({})  --删除所有文档
db.collection.drop()  --表示除了删除文档,并且也把集合和索引都删除

 注意:如果文档很多时,使用remove有的时候效率不高,这个时候推荐使用drop再创建集合,这样效率更高

3、mongodb的聚合操作

语法

db.COLLECTION_NAME.aggregate(<pipeline>, <options>)
--pipeline文档定义了操作中使用的聚合管道阶段和聚合操作符
--文档声明了一些聚合操作的参数

常见的聚合表达式

--字段路径表达式
$<field>  --使用$来指示字段路径
$<field>.<sub-field> --使用$和. 来指示内嵌文档字段路径

--系统变量表达式
$$<variable> --使用$$来指示系统变量
如$$CURRENT --指示管道中当前操作的文档(相当于this), $$CURRENT.<field>和$<field>是等效的


--常量表达式
$literal: <value> --指示常量<value>
$literal: "$name" --指示常量字符串“$name”

$project --对输入文档进行再次投影
$match --对输入文档进行再次筛选
$limit --筛选出管道内前N篇文档
$skip --跳过管道内第N篇文档
$unwind --展开输入文档中的数组字段
$sort --对输入的文档进行排序
$lookup --对输入的文档进行查询操作
$group --对输入的文档进行分组
$out --将管道中的文档进行输出

注意:以下的操作使用的数据如下

{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28 }
{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30 }
{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29 }

$project --对输入文档进行再次投影

> db.user.aggregate([{$project: {_id: 0, name: "$name.first", age: 1 }}])

--输出
--{ "name" : "bill", "age" : 28 }
--{ "name" : "ven", "age" : 30 }
--{ "name" : "even", "age" : 29 }
> db.user.aggregate([{$project: {_id: 0, age: 1, name:["$name.first", "$name.middle", "$name.last"]}}])

--输出,如果是没有的字段,那么会输出null
--{ "age" : 28, "name" : [ "bill", null, "yu" ] }
--{ "age" : 30, "name" : [ "ven", null, "yu" ] }
--{ "age" : 29, "name" : [ "even", null, "yu" ] }

$match --对输入文档进行再次筛选

$match中使用的文档筛选语法, 和读取文档时的筛选语法相同,与find时的筛选用法是一样的

> db.user.aggregate([{$match: {"name.first": "even"}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29 }

> db.user.aggregate([{$match: {$and: [{age: {$gte: 29}}, {age: {$lte: 30}}]}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30 }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29 }

--$match与$project进行配合使用
> db.user.aggregate([{$match: {$and: [{age: {$gte: 29}}, {age: {$lte: 30}}]}}, {$project: {_id: 0, age: 1, name: "$name.first"}}])
--{ "age" : 30, "name" : "ven" }
--{ "age" : 29, "name" : "even" }

注意: $match是一个很常用的聚合阶段,应该尽量在聚合管道的开始阶段应用$match, 这样可以减少后续阶段中需要处理的文档数量, 优化聚合操作的性能

$limit --筛选出管道内前N篇文档

$skip --跳过管道内第N篇文档

> db.user.find()
--{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28 }
--{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30 }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29 }

> db.user.aggregate([{$skip: 1}, {$limit: 1}])
--{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30 }

$unwind --展开输入文档中的数组字段

为了方便演示对原始数据做些调整

{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28 }
{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30, "param" : [ ] }
{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "param" : [ "aaa", "bbb" ] }
> db.user.aggregate([{$unwind: {path: "$param"}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "param" : "aaa" }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "param" : "bbb" }

注意:这个方法会把对应的数据拆开展示,但是不展示param为null  [], 或者不存在的情况

> db.user.aggregate([{$unwind: {path: "$param", includeArrayIndex: "index"}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "param" : "aaa", "index" : NumberLong(0) }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "param" : "bbb", "index" : NumberLong(1) }

添加索引字段

> db.user.aggregate([{$unwind: {path: "$param", includeArrayIndex: "index", preserveNullAndEmptyArrays: true}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28, "index" : null }
--{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30, "index" : null }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "param" : "aaa", "index" : NumberLong(0) }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "param" : "bbb", "index" : NumberLong(1) }

把没有该字段的数据也进行一次展开

$sort --对输入的文档进行排序

> db.user.aggregate([{$sort: {age: 1}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28 }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "param" : [ "aaa", "bbb" ] }
--{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30, "param" : [ ] }

注意:用法与上面写的sort的用法一样,1表示升序, -1表示降序

$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>
}

--同一数据库中的另一个查询集合

 

注意: 这个语法相当于mysql中的leftjoin的用法

当前的演示案例的数据

> db.user.find()
--{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28 }
--{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30 }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29 }

> db.info.find()
--{ "_id" : ObjectId("6118cb10f70c4e8e186d8a65"), "name" : "ven", "desc" : "this is ven desc" }
--{ "_id" : ObjectId("6118cb10f70c4e8e186d8a66"), "name" : "even", "desc" : "this even desc" }
--{ "_id" : ObjectId("6118cb10f70c4e8e186d8a67"), "name" : "bill", "desc" : "this is bill desc" }

 使用$lookup

> db.user.aggregate([{$lookup:{from: "info", localField: "name.first", foreignField: "name", as: "info"}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28, "info" : [ { "_id" : ObjectId("6118cb10f70c4e8e186d8a67"), "name" : "bill", "desc" : "this is bill desc" } ] } --{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30, "info" : [ { "_id" : ObjectId("6118cb10f70c4e8e186d8a65"), "name" : "ven", "desc" : "this is ven desc" } ] } --{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "info" : [ { "_id" : ObjectId("6118cb10f70c4e8e186d8a66"), "name" : "even", "desc" : "this even desc" } ] }

注意:如果localField是一个数组,那么info出来的数据也会是一一对应的数组, 那就可以配合$unwind进行使用,如果匹配不到,那么返回的是一个空数组

对字段进行筛选可以使用关键字pipeline

> db.user.aggregate([{$lookup: {from: "info", localField: "name.first", foreignField: "name", pipeline: [{$project: {_id: 0, desc: 1}}], as: "info"}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28, "info" : [ { "desc" : "this is bill desc" } ] }
--{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30, "info" : [ { "desc" : "this is ven desc" } ] }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "info" : [ { "desc" : "this even desc" } ] }
--{ "_id" : ObjectId("6118ceb2f70c4e8e186d8a68"), "name" : { "first" : "ctbu", "last" : "yu" }, "age" : 23, "info" : [ ] }

$lookup的复杂运用

$lookup: {
    from: <collection to join>,
    let: {<var_1>: <expression>, ..., <var_n>: <expression>},
    pipeline: [<pipeline to execute on the collection to join>],
    as: <output array field>  
}

--对查询集合中的文档使用聚合阶段进行处理, 这里的let是可选参数

 对查询集合中的文档使用聚合阶段进行处理时,如果需要参考管道文档中的字段, 则必须使用let参数对字段进行声明

> db.user.aggregate([{$lookup:{from: "info", pipeline: [{$project: {_id: 0, desc: 1}}], as: "info"}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28, "info" : [ { "desc" : "this is ven desc" }, { "desc" : "this even desc" }, { "desc" : "this is bill desc" } ] } --{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30, "info" : [ { "desc" : "this is ven desc" }, { "desc" : "this even desc" }, { "desc" : "this is bill desc" } ] } --{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "info" : [ { "desc" : "this is ven desc" }, { "desc" : "this even desc" }, { "desc" : "this is bill desc" } ] } --{ "_id" : ObjectId("6118ceb2f70c4e8e186d8a68"), "name" : { "first" : "ctbu", "last" : "yu" }, "age" : 23, "info" : [ { "desc" : "this is ven desc" }, { "desc" : "this even desc" }, { "desc" : "this is bill desc" } ] }

上面没有对条件做限定,只做了字段展示,所以会全部显示出来

> db.user.aggregate([{$lookup: {from: "info", let: {n: "$name.first"}, pipeline: [{$match: {$expr: {$eq: ["$name", "$$n"]}}}, {$project: {_id: 0, desc: 1}}], as: "info"}}])
--{ "_id" : ObjectId("6115afe39526539ff7a05d9f"), "name" : { "first" : "bill", "last" : "yu" }, "age" : 28, "info" : [ { "desc" : "this is bill desc" } ] }
--{ "_id" : ObjectId("6115afe39526539ff7a05da0"), "name" : { "first" : "ven", "last" : "yu" }, "age" : 30, "info" : [ { "desc" : "this is ven desc" } ] }
--{ "_id" : ObjectId("6115afe39526539ff7a05da1"), "name" : { "first" : "even", "last" : "yu" }, "age" : 29, "info" : [ { "desc" : "this even desc" } ] }
--{ "_id" : ObjectId("6118ceb2f70c4e8e186d8a68"), "name" : { "first" : "ctbu", "last" : "yu" }, "age" : 23, "info" : [ ] }

注意: 在上面的表达式中,let中的n是表示对user中的字段引用,而match中需要对两个字段的值进行比较,有用到$$n,所以要用表达式$expr,但是$eq的写法区别于上面的写法,因为这里要对两个表的值进行引用,所以采用的数组的写法,在引用定义的变量时需要用$$进行修饰, $name.first表示的是当前表的字段, pipeline表示的是连接表的信息

> db.user.aggregate([{$project: {_id: 0, name: "$name.first", age: 1}},{$lookup: {from: "info", let: {n: "$name.first", a: "$age"}, pipeline: [{$project: {_id: 0, name: 1, desc: 1}}, {$match: {$expr: {$and: [{$eq: ["$name", "$$n"]}]}}}], as: "info"}}])
--{ "age" : 28, "name" : "bill", "info" : [ { "name" : "bill", "desc" : "this is bill desc" } ] }
--{ "age" : 30, "name" : "ven", "info" : [ { "name" : "ven", "desc" : "this is ven desc" } ] }
--{ "age" : 29, "name" : "even", "info" : [ { "name" : "even", "desc" : "this even desc" } ] }
--{ "age" : 23, "name" : "ctbu", "info" : [ ] }

注意:以上写法有点失误,在pipeline里面需要先match再project, 不然很容易所匹配的字段被project后,match找不到,所以建议先match, 再project 

$group --对输入的文档进行分组

$group: {
    _id: <expression>,
   <field1>: { <accumulartor1> : <expression1> },
   ...
}

 注意:这里的_id表示用于分组的字段, 暂时没有发现可以其他字段进行操作

> db.goods.find()
--{ "_id" : ObjectId("611b3fd0ab556f9418ed89c6"), "name" : "apple", "price" : 12, "count" : 5 }
--{ "_id" : ObjectId("611b3fd0ab556f9418ed89c7"), "name" : "pear", "price" : 11, "count" : 20 }
--{ "_id" : ObjectId("611b3fd0ab556f9418ed89c8"), "name" : "banana", "price" : 16, "count" : 5 }
--{ "_id" : ObjectId("611b3fd0ab556f9418ed89c9"), "name" : "grape", "price" : 9.8, "count" : 63 }
--{ "_id" : ObjectId("611b4026ab556f9418ed89ca"), "name" : "apple", "price" : 12, "count" : 1 }
--{ "_id" : ObjectId("611b4026ab556f9418ed89cb"), "name" : "banana", "price" : 16, "count" : 80 }
--{ "_id" : ObjectId("611b4026ab556f9418ed89cc"), "name" : "grape", "price" : 9.8, "count" : 12 }
--{ "_id" : ObjectId("611b4046ab556f9418ed89cd"), "name" : "apple", "price" : 12, "count" : 1 }
--{ "_id" : ObjectId("611b4046ab556f9418ed89ce"), "name" : "banana", "price" : 16, "count" : 80 }
--{ "_id" : ObjectId("611b4046ab556f9418ed89cf"), "name" : "grape", "price" : 9.8, "count" : 12 }

> db.goods.aggregate([{$group: {_id: "$name", sum: {$sum: {$multiply: ["$price", "$count"]}}, avg: {$avg: "$price"}, max: {$max: "$count"}, min: {$min: "$count"}}}])
--{ "_id" : "pear", "sum" : 220, "avg" : 11, "max" : 20, "min" : 20 }
--{ "_id" : "apple", "sum" : 84, "avg" : 12, "max" : 5, "min" : 1 }
--{ "_id" : "banana", "sum" : 2640, "avg" : 16, "max" : 80, "min" : 5 }
--{ "_id" : "grape", "sum" : 852.6000000000001, "avg" : 9.8, "max" : 63, "min" : 12 }

注意: 这里的$sum表示求和, $multiply表示求积, $avg表示求平均数, $max表示求最大值 , $min表示求最小值, 如果是$sum:1则是表示求文档的数量

> db.goods.aggregate([{$group: {_id: "$name", count: {$sum: 1}}}])
--{ "_id" : "banana", "count" : 3 }
--{ "_id" : "apple", "count" : 3 }
--{ "_id" : "grape", "count" : 3 }
--{ "_id" : "pear", "count" : 1 }

 使用聚合函数求所有的文档的聚合值(把_id设置为null)

> db.goods.aggregate([{$group: {_id: null, count: {$sum: 1}, sum: {$sum: {$multiply: ["$price", "$count"]}}}}])
--{ "_id" : null, "count" : 10, "sum" : 3796.6 }

--把所有名称都放到一个数组里
> db.goods.aggregate([{$group: {_id: null, count: {$sum: 1}, sum: {$sum: {$multiply: ["$price", "$count"]}}, collect: {$push: "$name"}}}])
--{ "_id" : null, "count" : 10, "sum" : 3796.6, "collect" : [ "apple", "pear", "banana", "grape", "apple", "banana", "grape", "apple", "banana", "grape" ] }

--把所有名称都放到一个数组里,并且不重复
> db.goods.aggregate([{$group: {_id: null, count: {$sum: 1}, sum: --{$sum: {$multiply: ["$price", "$count"]}}, collect: {$addToSet: "$name"}}}])
{ "_id" : null, "count" : 10, "sum" : 3796.6, "collect" : [ "banana", "pear", "apple", "grape" ] }

 $out --将管道中的文档进行输出

> db.goods.aggregate([{$group: {_id: null, count: {$sum: 1}, sum: {$sum: {$multiply: ["$price", "$count"]}}, collect: {$addToSet: "$name"}}}, {$out: "output"}])

注意: 该操作会把数据结果写入到output这个新的文档中

通常来讲, 每个聚合管道阶段使用的内存不能超过100MB,如果超过了就会报错,为了让程序执行下去可以添加 allowDiskUse: true配置,当内存不足时,会将操作数据写入临时文件中,这个临时文件会被写入dbPath下的_tmp文件夹下,默认值是/data/db

> db.goods.aggregate([{$group: {_id: "$name"}}],{allowDiskUse: true})
{ "_id" : "banana" }
{ "_id" : "pear" }
{ "_id" : "apple" }
{ "_id" : "grape" }

注意:对数组值的引用常见于 $project, $unwind, $lookup, $group

复杂例子操作

数据库

> show collections
--bag
--goods
--user
> db.bag.find()
--{ "_id" : ObjectId("612c140a2958f075bfdde1a8"), "name" : "bill", "goods" : "apply", "count" : 3 }
--{ "_id" : ObjectId("612c140a2958f075bfdde1a9"), "name" : "even", "goods" : "pear", "count" : 2 }
--{ "_id" : ObjectId("612c140a2958f075bfdde1aa"), "name" : "ven", "goods" : "grape", "count" : 6 }
--{ "_id" : ObjectId("612c140a2958f075bfdde1ab"), "name" : "bill", "goods" : "orange", "count" : 12 }
> db.goods.find()
--{ "_id" : ObjectId("612c113f2958f075bfdde1a1"), "name" : "apply", "price" : 11 }
--{ "_id" : ObjectId("612c113f2958f075bfdde1a2"), "name" : "pear", "price" : 12 }
--{ "_id" : ObjectId("612c113f2958f075bfdde1a3"), "price" : 13, "name" : "grape" }
--{ "_id" : ObjectId("612c113f2958f075bfdde1a4"), "name" : "orange", "price" : 10 }
> db.user.find()
--{ "_id" : ObjectId("612c144a2958f075bfdde1ac"), "name" : "bill", "age" : 18 }
--{ "_id" : ObjectId("612c144a2958f075bfdde1ad"), "name" : "ven", "age" : 12 }
--{ "_id" : ObjectId("612c144a2958f075bfdde1ae"), "name" : "even", "age" : 28 }

进行嵌套查询

db.bag.aggregate([
    {$project: {_id: 0, name: 1, goods: 1, count: 1}},
    {
        $lookup: {
            from: "goods", 
            let: {g: '$goods', c: '$count'}, 
            pipeline: [{$match: {$expr: {$eq: ["$name", "$$g"]}}}, {$project: {_id: 0, price: 1, sum: {$multiply: ['$$c', '$price']}}}], 
            as: "msg"
        }
    },
    {
        $lookup: {
            from: "user", 
            let: {n: "$name"}, 
            pipeline: [{$match: {$expr: {$eq: ["$name", "$$n"]}}}, {$project: {_id: 0, age: 1}}], 
            as: "user"
        }
    },
])

结果:

--{ "name" : "bill", "goods" : "apply", "count" : 3, "msg" : [ { "price" : 11, "sum" : 33 } ], "user" : [ { "age" : 18 } ] }
--{ "name" : "even", "goods" : "pear", "count" : 2, "msg" : [ { "price" : 12, "sum" : 24 } ], "user" : [ { "age" : 28 } ] }
--{ "name" : "ven", "goods" : "grape", "count" : 6, "msg" : [ { "price" : 13, "sum" : 78 } ], "user" : [ { "age" : 12 } ] }
--{ "name" : "bill", "goods" : "orange", "count" : 12, "msg" : [ { "price" : 10, "sum" : 120 } ], "user" : [ { "age" : 18 } ] }

 

posted on 2021-07-23 07:37  even_blogs  阅读(283)  评论(0编辑  收藏  举报