工作中常用的MongoDB语法整理

工作中常用的Mongo

  上一篇博客介绍了一些MongoDB的基本语法,博客链接如下:
MongoDB基本语法

本来想继续对基本增删改查之外的MongoDB语法进行总结,
但是考虑到如下原因 :

  1. 有太多热爱分享的人已经进行过总结
  2. 即便有基础的语法介绍和示例,部分读者可能还是不知道怎么应用

所以决定从我工作中用到的语法出发,用实际需求来推动学习
现将一些比较常用的语法进行整理,分享给大家~

本篇博客要点如下:

准备工作:

语法整理:

数据准备

自己伪造十条交易数据,后续操作都是以这10条数据举例,
造数语法如下:

db.getCollection('test_mongo').insertMany([
{
    "日期" : "20190604","卡号" : "0000000000000000","LAST_UP_TIME" :"20190612192659","交易流水号" : "254711015127","交易商户号" : "测试商户", "结算商户号" : "测试商户","交易手续费" : 1.6,"交易金额" : 233.4
},
/* 2 */
{
    "日期" : "20190605","卡号" : "0000000000000001", "LAST_UP_TIME" : "20190612192659",  "交易流水号" : "254711015128","交易商户号" : "测试商户", "结算商户号" : "测试商户","交易手续费" : 1.6, "交易金额" : 233.4
},
/* 3 */
{
    "日期" : "20190604","卡号" : "0000000000000002","LAST_UP_TIME" : "20190612192659","交易流水号" : "254711015129","交易商户号" : "测试商户","结算商户号" : "测试商户", "交易手续费" : 1.6,"交易金额" : 233.4
},
/* 4 */
{
    "日期" : "20190607","卡号" : "0000000000000003","LAST_UP_TIME" : "20190612192659","交易流水号" : "254711015130","交易商户号" : "测试商户","结算商户号" : "测试商户", "交易手续费" : 1.6,"交易金额" : 233.4
}
,
/* 5 */
{
   "日期" : "20190608", "卡号" : "0000000000000004","LAST_UP_TIME" : "20190612192659","交易流水号" : "254711015131","交易商户号" : "测试商户","结算商户号" : "测试商户","交易手续费" : "1.6","交易金额" : 233.4
}
,
/* 6 */
{
    "日期" : "20190609", "卡号" : "0000000000000005","LAST_UP_TIME" : "20190612192659", "交易流水号" : "254711015132","交易商户号" : "测试商户", "结算商户号" : "测试商户","交易手续费" : 1.6,"交易金额" : 233.4
}
,
/* 7 */
{
    "日期" : "20190610","卡号" : "0000000000000006","LAST_UP_TIME" : "20190612192659","交易流水号" : "254711015127","交易商户号" : "测试商户","结算商户号" : "测试商户","交易手续费" : 1.6,"交易金额" : 233.4
}
,
/* 8 */
{ 
   "日期" : "20190611", "卡号" : "0000000000000006", "LAST_UP_TIME" : "20190612192659", "交易流水号" : "254711015134","交易商户号" : "测试商户","结算商户号" : "测试商户","交易手续费" : 41.6,"交易金额" : 2334.4
}
,
/* 9 */
{
  "日期" : "20190611", "卡号" : "0000000000000007","LAST_UP_TIME" : "20190612192659","交易流水号" : "254711015135","交易商户号" : "测试商户","结算商户号" : "我不是测试商户","交易手续费" : 12.6, "交易金额" : 2333.4
}
,
/* 10 */
{
  "日期" : "20190612","卡号" : "0000000000000008","LAST_UP_TIME" : "20190612192659","交易流水号" : "254711015136", "交易商户号" : "测试商户","结算商户号" : "我是测试商户","交易手续费" : 11.6,"交易金额" : 2233.4
}])
常用语法整理
索引创建

Q : 当表数据量比较大时,查询效率严重下降,该怎么办呢?
A : 当然是创建索引了,索引创建之后(查询时间可能会从几个小时,降低到几十秒), 数据量越大性能提升越明显,亲测有效

创建索引办法:

// 根据日期创建索引
db.getCollection('test_mongo').ensureIndex({日期: 1})

创建之后,索引如下所示 (其中_id是mongo默认的索引):
索引详情

Q : 我用你说的方法创建索引, 查询效率确实快很多呢~ 不过, 集群在我创建索引的时候处于崩溃状态,我要被领导和同事怼死了,怎么办?
A: 默认情况下,MongoDB的ensureIndex()是阻塞型操作,会暂停数据库上所有正在进行的其他操作,直到创建索引完成。但是,在高于或等于1.3.2版本的MongoDB中,提供了可选的后台创建索引的选项。 所以,在生产环境下,一定要选用后台创建索引的方式哦,不然你就死翘翘啦!

后台创建索引:

// 根据交易流水号后台创建索引
db.getCollection('test_mongo').ensureIndex({交易流水号: 1},{background : true})

创建结果:
Q: 我们的查询条件很复杂,要根据好多字段查询,难道我要一个一个的创建索引么? 话说后台创建索引也是很耗时间的!
A: 你可以创建联合索引啊

创建联合索引:

// 根据卡号和交易商户号创建联合索引
db.getCollection('test_mongo').ensureIndex({卡号: 1,交易商户号:1 },{background : true})

创建结果如下:
联合索引
Q : 你上面给我看的索引都是通过工具截图
那不方便使用工具的时候我能看表里的索引么?
A : 当然可以啦,使用如下语法即可! 执行结果就不展示啦

// 查看表格索引
db.getCollection('test_mongo').getIndexes()
内存溢出

Q : 我在对某些字段做排序或者是其它操作时抛出了类似这样的错误
exception: getMore runner error: Overflow sort stage buffered data
usage of 33638076 bytes exceeds internal limit of 33554432 bytes

我该怎么办呢?

A: 这是操作占用的内存超过Mongo的限制了(排序操作最容易暴露这个问题),有如下几种解决方案可供参考
可以借助磁盘来实现查询,不过性能会相对比较低哦

// 使用硬盘根据日期降序排列
db.getCollection('test_mongo').find({}).sort({日期: -1},{allowDiskUse: true})

通过创建索引,不仅能解决这个问题,而且查询的性能还能大大提升哦

不过,索引不是万能的,添加索引也会带来一定的弊端,这样会导致数据插入的时候相对之前较慢,因为索引会占据空间的

当然,还可以通过增大排序内存的方法,但是数据量如果再大呢,内存溢出的问题还是会暴露出来,所以不推荐使用这个方法

数据迁移

Q : 因为业务需要,想把部分数据从一张表里迁移到另一张表里,我应该怎么办呢?
A : 这个容易,使用下面的语法就可以了
将数据从一张表里面复制到另一张表

	// 将满足查询条件的数据从一张表复制到另一张表,表格不存在会自动创建
	db.getCollection('test_mongo').find({}).forEach(function(copy) {
        db.test3_mongo.save(copy)
}); 

执行结果如下图 :
数据迁移上面用到了forEach语法,下面简单介绍一下 :
MongoDB数据库forEach语句循环遍历功能是非常常用的一个功能。
语法如下 :

forEach(function(方法名){
方法体
});

采用forEach循环遍历,并每次循环允许执行一次回调函数
这里我们回调函数里面执行的是简单的save操作,
回调函数里面可以执行其它更复杂的场景,可根据业务需要灵活调用.

批量更新

Q : 你的上一篇博客介绍到了updateMany这种批量更新的方式,但是使用有比较大的局限性,有没有更灵活的批量更新方式呢?
A : 当然有了,而且通过之前介绍的update就可以实现呢~

// 把日期为20190608或20190604数据的交易状态修改为1
db.getCollection('test_mongo').update(
{日期:{$in:['20190608','20190604']}}, 
{$set:{'交易状态': NumberInt(1)}}, 
{multi:true,upsert:false} // 批量更新
)

介绍一下这个语句里面涉及到的两个参数,官方介绍如下 :
upsert: Optional. If set to true, creates a new document when no document matches the query criteria. The default value is false, which does not insert a new document when no match is found.

multi: Optional. If set to true, updates multiple documents that meet the query criteria. If set to false, updates one document. The default value is false.

翻译过来就是 :

**upsert:**可选的。 如果设置为true,则在没有文档与查询条件匹配时创建新文档。 默认值为false,如果未找到匹配项,则不会插入新文档。
multi: 可选的。 如果设置为true,则更新符合查询条件的多个文档。 如果设置为false,则更新一个文档。 默认值为false。

现在,我们应该对这两个参数的用法一目了然了.

Q : 由于取值逻辑出现错误, 2019年6月9日所有交易的交易金额都比实际少了一元, 我该怎么把它们都给掰正呢?
A : 在操作的字段取值为数字的情况下,可以使用 $inc帮你解决问题哦! 看我操作吧

  // 将20190609所有交易的交易金额 -1
 db.getCollection('test_mongo').update({ "日期": "20190609" },
{ $inc:{交易金额: -1 }}, 
{multi:true,upsert:false} // 批量更新
);

执行结果 : Updated 1 existing record(s) in 8ms

聚合操作

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

因篇幅所限, 不对每一种用法进行具体介绍, 通过工作中的几个业务场景来介绍如何使用聚合操作

Q: 最近收到投诉,部分商户的一笔交易,出现了多条数据,被怼死啦,我想知道这样的数据有哪些?该怎么办啊~
A: 你们的一笔交易是根据什么进行区分的啊?
Q: 通过交易流水号能够确定出唯一的一笔交易
A: 那简单,交给我,看我用聚合操作给你搞定

// 查询出同一个交易流水号超过2条的记录
db.getCollection('test_mongo').aggregate([
    {'$group' : {_id : '$交易流水号' , ordercount : {$sum : 1}}},
    {'$match': {ordercount : {$gte : 2}}}
    ],{allowDiskUse : true})

结果如下 : 这里显示的 _id : 指的就是出现重复的交易流水号
ordercount 指的是该交易流水号出现的次数,
接下来只要定位到这些数据就OK啦

 /* 1 */
{
    "_id" : "254711015127",
    "ordercount" : 2.0
}

Q : 问题又来了, 领导让我查2019年6月份的交易数据总条数
A : 那么6月份数据的标志是什么呢?
Q : 日期的前6位是201906
A : 嗯,我来试试吧

	// 统计表格里日期字段前6位是201906的数据条数
    db.getCollection('test_mongo').aggregate([
         {$match:{'日期':{$gte:'201906', $lt : '201907'}}},
        {$project:{'CREATETIMEDAY': { $substr: [ "$日期", 0, 6] }}},
{$group:{_id:"$CREATETIMEDAY",count:{$sum:1}}}
])

统计结果如下图所示: 即: 2019年6月份的交易数据有10条

/* 1 */
{
    "_id" : "201906",
    "count" : 10.0
}

Q : 领导又给了我一个商户名称,让我统计这个商户的交易笔数,交易手续费,和交易金额
A : 你还真是麻烦,我来给你统计吧

// 查询交易商户号为测试商户的,交易笔数,交易手续费,交易总金额
db.getCollection('test_mongo').aggregate([
      {$match:{ 交易商户号:"测试商户"}},
      {$group:{_id:{商户名称:"$交易商户号"},
       "交易笔数":{$sum: 1},
       "总手续费":{$sum:"$交易手续费"},
       "总交易金额":{$sum:"$交易金额"},
      }}
    ])

输出结果如下所示:

/* 1 */
{
    "_id" : {
        "商户名称" : "测试商户"
    },
    "交易笔数" : 10.0,
    "总手续费" : 75.4,
    "总交易金额" : 8534.0
}
表内字段关联

Q : 我想通过表内的部分字段进行关联查询操作,该怎么办呢?
A : 可以通过where语法来实现 :

// 查询同一张表里面 交易商户号和结算商户号不同的数据
db.getCollection("test_mongo").find({"$where" : "this.交易商户号 != this.结算商户号"})

查询结果如下所示 :

/* 1 */
{
    "_id" : ObjectId("5d02fbf74af09a8f0f72ad19"),
    "日期" : "20190612",
    "卡号" : "0000000000000008",
    "LAST_UP_TIME" : "20190612192659",
    "交易流水号" : "254711015136",
    "交易商户号" : "测试商户",
    "结算商户号" : "我是测试商户",
    "交易手续费" : 11.6,
    "交易金额" : 2233.4
}

/* 2 */
{
    "_id" : ObjectId("5d02fbf74af09a8f0f72ad18"),
    "日期" : "20190611",
    "卡号" : "0000000000000007",
    "LAST_UP_TIME" : "20190612192659",
    "交易流水号" : "254711015135",
    "交易商户号" : "测试商户",
    "结算商户号" : "我不是测试商户",
    "交易手续费" : 12.6,
    "交易金额" : 2333.4
}
前缀过滤

Q: 我想查询某个字段的前缀为指定字符串的数据,该怎么做呢?
A:请看下面的示例

// 查找指定前缀的数据
db.getCollection("test_mongo").find({_id:{$regex:'ID_'}},{_id:1})

部分结果如下:

{ 
    "_id" : "ID_20190828_20190829000147773836"
}
{ 
    "_id" : "ID_20190828_20190829000147773838"
}
{ 
    "_id" : "ID_20190828_20190829000147773839"
}
{ 
    "_id" : "ID_20190828_20190829000147773840"
}
{ 
    "_id" : "ID_20190828_20190829000147773857"
}
{ 
    "_id" : "ID_20190828_20190829000147773860"
}
{ 
    "_id" : "ID_20190828_20190829000147773864"
}
{ 
    "_id" : "ID_20190828_20190829000147773875"
}
{ 
    "_id" : "ID_20190828_20190829000147773889"
}

Q:那如果我想查询某个字段不以指定前缀开始的数据呢?
A:也是有办法做到的

// 查找不以指定前缀开始的数据,用not操作符 + 正则表达式即可
db.getCollection("test_mongo").find({_id:{$not:/^ID_/}},{_id:1})

部分结果如下:

{ 
    "_id" : ObjectId("5cb524d74404f9362cc4dbd1")
}
{ 
    "_id" : ObjectId("5cb524d24404f9362cc38ae9")
}
{ 
    "_id" : ObjectId("5cb524d74404f9362cc527f6")
}
{ 
    "_id" : ObjectId("5cb524d74404f9362cc4f980")
}
{ 
    "_id" : ObjectId("5cb524da4404f9362cc7926d")
}
根据一张表的字段更新另一张表

Q: 我又又有有问题了,我现在需要从一张表里查出数据,用查到的结果去更新另一张表,该怎么做呢?
A: 请看下面的示例

// 用日期和交易流水(唯一)做匹配,用test_mongo里的字段FIELD1,FIELD2去更新test2_mongo里面的数据
// 这里面有一个需要注意的地方,如果集群做了分片,一定要使用批量更新的办法,不然会更新失败
db.getCollection('test_mongo').find({}).forEach(
        function(item){                 	 db.getCollection('test2_mongo').updateMany({"AC_DT":item.AC_DT,"SREF_NO":item.SREF_NO},
           {$set:{"FIELD1": item.FIELD1,"FIELD2":item.FIELD2}});
    }   
)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值