MongoDB数据库普通查询/聚合操作/更新/去重操作合集,顺便记录筛选过滤查询操作和db.updae()更新数据库记录遇到的坑。持续更新,超详细!
缘由:使用MongoDB时遇到一些需要查询/更新操作指定某些字段的业务场景
- 查询和更新指定字段就需要进行简单的筛选和过滤,也能在大数据量时减少查询消耗时间
一、普通查询/更新等操作
1. 查询数据库某些指定字段,同时默认返回_id字段
db.users.find({},{ username: 1})
以上查询为只查询users表中的所有数据的username字段,但每条查询到的记录默认还会返回_id
2. 查询数据库某些指定字段,并不返回_id字段
db.users.find({},{ username: 1, _id: 0 })
以上查询为只查询并只返回users表中的所有数据的username字段,_id字段也不会返回
语法为db.[collectionName].find({查询器},{筛选器}),通过设置字段的1或0来进行筛选
3. update()方式更新数据库某些记录的某些指定字段,这里遇到了坑,慎用db.[collectionName].update()
db.users.update({'username':'John'},{ 'age': 24})
以上更新为查找到users表中username为John的记录并更新其age字段值为24;
特别注意:使用update更新会把users表中username为John的记录,除_id的其他字段全部清空,只会保留_id字段和刚刚更新的age字段,所以需要慎用;
因为update这种文档更新方式,会用新的记录直接代替旧记录,所以需要慎用。
语法为db.[collectionName].update({查询器},{修改器})
4. $set方式更新数据库某些记录的某些指定字段
db.users.update({'username':'John'},{$set:{'age': 24}})
以上更新为查找到users表中username为John的记录并更新其age字段值为24;
$set修改器用来指定一个键值对,如果存在键就进行修改不存在则进行添加。
5. insertOrUpdate更新方式(查询器查出来数据就执行更新操作,查不出来就替换操作)
db.[collectionName].update({查询器},{修改器},true)
语法:第三个参数设置为true,代表insertOrUpdate,即存在即更新,否则插入该数据。
6. 批量更新方式(因为默认情况下,当查询器查询出多条符合条件的数据时,默认修改第一条数据,不能批量更新)
db.[collectionName].update({查询器},{修改器},false, true)
语法:添加第四个参数,该参数为true,则批量更新,为false,则更新一条。
7. $inc数字类型修改器
db.users.update({'username':'John'},{$inc:{'age': 1}})
以上更新为查找到users表中username为John的记录并在其age字段值在原来的数值上进行加一后更新,字段后设置为 -1 则为在原来的数值上减一后更新;
$inc修改器仅用于数字类型字段,他可以为指定的键对应的数字类型的数值进行加减操作。
8. 数据模型新增了字段和字段值,为之前的数据批量添加新字段和新字段值
db.getCollection('数据表名').updateMany({'新字段名':{'$exists':false}},{'$set':{'新字段名': 新字段值}})
例如:db.getCollection('users').updateMany({'age':{'$exists':false}},{'$set':{'age':18}})
以上更新为查找到users表记录中没有age字段的记录,并添加age字段到记录中,age字段值设置为18。
9. 数据库查找当前数据表中字段有不重复数据的记录条数
db.数据表名.distinct("字段名").length
例如:db.users.distinct("testid").length
以上为查找users表记录中testid字段重复的次数。
10. 数据库查找当前数据表中字段有重复数据的具体记录并展示
db.users.aggregate([
{"$group" : { "_id": "$testid", "count": { "$sum": 1 } } },
{"$match": {"_id" :{ "$ne" : null } , "count" : {"$gt": 1} } },
{"$project": {"testid" : "$_id", "_id" : 0} }
]);
以上为查找users表记录中testid字段重复的具体记录,结果会展示重复的testid字段值,有多条重复记录则会展示多个testid
11. 数据库批量更新时间类型字段
注:建议使用下面这种以非布尔值的查询器来匹配结果
db.users.updateMany({'testid':'123456'},{$set:{'creatAt': ISODate("2021-02-07 02:20:00"),'updateAt':ISODate("2020-02-07 02:30:00")}})
注:下面这种以布尔值为查询器匹配结果的写法可能获取不到查询结果:
db.users.updateMany({'enbale':true},{$set:{'creatAt': ISODate("2021-02-07 02:20:00"),'updateAt':ISODate("2020-02-07 02:30:00")}})
以上为查找并批量更新users表记录中testid字段值为123456的记录,并更新creatAt字段值为2021年02月07日10点20分零秒,更新updateAt字段值为2021年02月07日10点30分零秒。
ps:mongo存储的时间格式会比北京时间早8个小时,意思是我上面数据库操作命令中填写的02:20:00代表的是10:20:00,且注意数据库操作命令中,年月日,小时分秒的位数都是2位,例如2月7日要写为02-07
12. 筛选查询某些记录中的字段值在特定范围内的操作,$in包含查询,$nin不包含,$ne不为
db.users.find({'testid':{$in: [1, 2, 3]}})
db.users.find({'testid':{$nin: [1, 2, 3]}})
db.users.find({'testid':{$ne: '1'})
第一个$in查询表示,查找users集合中testid的值为[1, 2, 3]其中值的记录,意为包含;
第二个$nin查询表示,查找users集合中testid的值不为[1, 2, 3]其中值的记录,意为不包含;
第三个$ne查询表示,查找users集合中testid的值不为'1'的记录,意为不为。
二、aggregate聚合查询/更新等操作
1. mongo数据库聚合查询
db.users.aggregate([
{
$group: {
_id: '$testid',
count: {$sum: 1},
dups: {$addToSet: '$_id'}
}
},{
$match: {count: {$gt: 1}}
}
],{
allowDiskUse: true
});
以上表示将users表按照testid字段进行聚合分组查询,查询结果会聚合分组成多个记录对象:{_id:'需要进行分组的testid字段',count:'testid字段值的出现次数',dups:['testid字段值对应的记录的ObjectId','xxx']}
dups对应的是testid字段值对应的记录的ObjectId列表(可以为1个,也可以为多个),可以在后面继续进行聚合操作使用
命令解析1. users是数据库集合名,$group 根据testid分组并统计数量,且只会返回参与分组的字段,可以使用$addToSet在返回结果数组中增加_id字段
命令解析2. $match: {count: {$gt: 1}} 表示只找出字段值出现次数大于1的重复数据
命令解析3. allowDiskUse: true 表示对使用磁盘进行聚合操作, 否则数据量太大会撑爆内存, 导致程序终止
2. mongo数据库聚合查询后再对查询结果进行批量操作
db.users.aggregate([
{
$group: {
_id: '$testid',
count: {$sum: 1},
dups: {$addToSet: '$_id'}
}
},{
$match: {count: {$gt: 1}}
}
],{
allowDiskUse: true
}).forEach(function(doc){
doc.dups.shift();
db.users.update({'_id':{$in: doc.dups}},{'$set':{'tryid':null,'state':0}})
});
forEach后面即为对查询结果进行批量操作,以上代码为将聚合查询后的结果进行批量更新,将查询结果按照之前dups对应的_id字段值列表,进行查询后再更新,更新tryid字段值为null,state字段值为0
doc.dups.shift() 表示从dups数组中第一个值开始;作用是踢除dups数据其中一个_id,让后面的操作语句不会操作所有数据,可以理解为操作一个将dups的这个_id清除一个
3. mongo数据库按照月份统计指定条件的数据
db.records.aggregate([
{
"$match": {
"createdAt": {
$gte: ISODate("2021-02-01T00:00:00.000Z"),
$lte: ISODate("2021-05-01T00:00:00.000Z")
},
state: 1,
token: {$ne:null}
}
}, {
"$group": {
"_id": {
"month": {
"$month":"$createdAt"
}
},
state:{$sum:1}
}
}
]);
查询结果为:
{ "_id" : { "month" : 4 }, "state" : 399 }
{ "_id" : { "month" : 3 }, "state" : 7 }
$match里面可以理解为查询器,填写查询条件,$group还是分组条件,上面的例子表示,新建组名month,并按照createdAt字段按月分别统计,并求出各月份查询到的记录state参数值总和
前面查询语句设置的条件state为1,所以也可以理解为新建组名month,并按照createdAt字段按月分别统计,并求出各月份查询到的记录个数
需要注意,查询器的时间范围设置,1号为起始时间,下月1号位终止时间。如上设置,起始和终止时间均为1号统计的才是真实的每月数据,否则会多算或漏算其他月份的数据
以上时间范围为2月1日至5月1日,但最终结果并没有2月份的数据,这是因为根据查询器的createdAt时间范围,state为1,token不为null的查询结果,2月份的确没有符合的数据
4. mongo数据库按照日期格式(xxxx-xx-xx)来进行每天分组统计指定条件的数据
db.records.aggregate([
{
$match: {
state: 1,
}
},
{
$group: {
_id: {'$dateToString':{'format':'%Y-%m-%d','date':{ $add: ['$createdAt', 28800000] }}},
price: { $sum: '$price' },
count: { $sum: 1 }
}
}
]);
查询结果为:
{ "_id" : "2021-07-25", "price" : 981000, "count" : 116 }
{ "_id" : "2021-07-27", "price" : 193000, "count" : 14 }
{ "_id" : "2021-07-26", "price" : 1343200, "count" : 113 }
{ "_id" : "2021-07-20", "price" : 926800, "count" : 96 }
- $match里面可以理解为查询器,填写查询条件,$group还是分组条件,上面的例子表示,新建组名_id, price, count, 并按照createdAt字段按每天(日期格式为xxxx-xx-xx)进行统计
- 并按照日期对price和count进行分别统计,其中组名price的值是当天所有字段state值为1的记录的price字段值的总和,count则为当天所有字段state值为1的记录总条数
- 需要注意,date后面的$add: ['$createdAt', 28800000] 表示将查询到记录的字段createdAt值都加8个小时,因为mongo的时间很多时候会比北京时间慢8个小时,所以在查询分组是进行了时间的增加,但并没有更改数据库的字段createdAt值
- 如果不需要对时间进行操作,则直接将'date':{ $add: ['$createdAt', 28800000] } 替换为'date': '$createdAt'即可
5. mongo数据库按照月份(xxxx-xx)统计指定条件的数据
db.records.aggregate([
{
$match: {
state: 1,
}
},
{
$group: {
_id: {'$dateToString':{'format':'%Y-%m','date':{ $add: ['$createdAt', 28800000] }}},
price: { $sum: '$price' },
count: { $sum: 1 }
}
}
]);
查询结果为:
{ "_id" : "2021-06", "price" : 11759200, "count" : 974 }
{ "_id" : "2021-07", "price" : 12194200, "count" : 1240}
- $match里面可以理解为查询器,填写查询条件,$group还是分组条件,上面的例子表示,新建组名_id, price, count, 并按照createdAt字段按每月(日期格式为xxxx-xx)进行统计
- 并按照日期对price和count进行分别统计,其中组名price的值是当月所有字段state值为1的记录的price字段值的总和,count则为当月所有字段state值为1的记录总条数
- 需要注意,date后面的$add: ['$createdAt', 28800000] 表示将查询到记录的字段createdAt值都加8个小时,因为mongo的时间很多时候会比北京时间慢8个小时,所以在查询分组是进行了时间的增加,但并没有更改数据库的字段createdAt值
- 如果不需要对时间进行操作,则直接将'date':{ $add: ['$createdAt', 28800000] } 替换为'date': '$createdAt'即可
三、MongoDB操作符之$elemMatch(数组查询使用)
1. mongo数据库根据数组类型字段进行查询
例子:数据库user只有一条mongo记录:
{
"_id" : ObjectId("6088d778709382d134f5378f"),
"numid" : "373575",
"name" : '测试',
"friends" : [
{
"name" : "张三",
"state" : 0,
"addFriendAt": 2021-01-01T00:00:00.000+00:00
},
{
"name" : "李四",
"state" : 1,
"addFriendAt": 2021-02-01T00:00:00.000+00:00
},
{
"name" : "王麻子",
"state" : 2,
"addFriendAt": 2021-03-01T00:00:00.000+00:00
}
],
"country": "中国"
}
- 需求:我们现在需要通过列表类型字段friends中的name,state,addFriendAt三个字段来查询是否有满足的记录;
- 需要通过查询friends中的name为"李四",state:1,addFriendAt大于2021-03-01T00:00:00.000+00:00来查询是否有满足的记录;
- 很明显friends中name为"李四",state:1的记录,addFriendAt为2021-02-01T00:00:00.000+00:00,小于2021-03-01T00:00:00.000+00:00,所以这条记录查不到。
但是使用普通的查询语句,得到的结果却不是这样:
db.users.find({
"friends.name": "李四",
"friends.state": 1,
"friends.addFriendAt": {$gte: 2021-03-01T00:00:00.000+00:00}
})
- 以上查询居然能查到上面的记录,这是因为我们使用的查询器字段均为数组类型字段的二级字段;
- 例如:friends中的name,state,addFriendAt;
2. 当需要查询器里所有数组类型字段的二级字段均满足查询条件时则需要使用$elemMatch操作符。
通过$elemMatch操作符正确的查询数据:
db.users.find({
"friends": {
"$elemMatch": {
"name": "张三",
"state": 1,
"addFriendAt": {$lte: 2021-03-01T00:00:00.000+00:00}
}
}
})
这样得到的查询结果就为空了