mongo对文档中数组进行过滤的三种方法

前言
在mongo中数据类型有很多种,常见的包括:
| 数据类型 | 例子 | 描述 |
|---|---|---|
| String | { "x" : "foot" } |
字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。 |
| Integer | { "x" : 1 } |
整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。 |
| Object | { "x" : { "y" : "foot" } } |
用于内嵌文档 |
| Array | { "x" : [ "a" , "b" ] } |
用于将数组或列表或多个值存储为一个键。 |
有一种很常见的查询,就是过滤数组中的一些数据,只返回符合要求的数据。数据如下,将下面travel中的vehicle=train的记录保留,过滤掉其他的元素,并返回整个文档。
{
"name": "tom",
"travel": [
{
"vehicle" : "train",
"city" : "北京"
},
{
"vehicle" : "plane",
"city" : "上海"
},
{
"vehicle" : "train",
"city" : "深圳"
}
]
}
想要实现数组的过滤有三种方法,包括:
- 聚合查询 使用
$unwind将travel数组打散,获取结果集后用$match筛选符合条件的数据,最后使用$group进行聚合获取最终结果集 - 聚合查询 使用
$match过滤符合条件的根文档结果集,然后使用$project返回对应字段的同时,在travel数组中使用$filter进行内部过滤,返回最终结果集 - 普通查询 先筛选记录,然后通过投影查询过滤数组
下面来分析这三种方法能否实现需求。
添加数据
假设有两条记录,每条记录是一个人的信息,包括姓名、职业、旅游过的城市。旅游过的城市是一个数组,包含城市的名字以及交通工具。
db.test.insertOne({
"uid" : "1000001",
"name" : "zhangsan",
"job": "coder",
"travel" : [
{
"vehicle" : "train",
"city" : "北京"
},
{
"vehicle" : "plane",
"city" : "上海"
},
{
"vehicle" : "train",
"city" : "深圳"
}
]
})
db.test.insertOne({
"uid" : "1000002",
"name" : "lisi",
"job": "coder",
"travel" : [
{
"vehicle" : "plane",
"city" : "北京"
},
{
"vehicle" : "car",
"city" : "上海"
},
{
"vehicle" : "train",
"city" : "深圳"
}
]
})
db.test.find()
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
uid: '1000001',
name: 'zhangsan',
job: 'coder',
travel:
[ { vehicle: 'train', city: '北京' },
{ vehicle: 'plane', city: '上海' },
{ vehicle: 'train', city: '深圳' } ] }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
uid: '1000002',
name: 'lisi',
job: 'coder',
travel:
[ { vehicle: 'plane', city: '北京' },
{ vehicle: 'car', city: '上海' },
{ vehicle: 'train', city: '深圳' } ] }
验证三种方法
需求说明
现在的目标是:筛选的出所有记录中通过火车去旅游的城市,也就是travel数组中vehicle=train的记录,过滤掉非目标记录。
方法一
方法一:使用$unwind将travel数组打散,获取结果集后用match筛选符合条件的数据,最后使用$group进行聚合获取最终结果集。
db.getCollection('test').aggregate(
[
{
$unwind: "$travel"
},
{
$match : {
"job":"coder",
"travel.vehicle": "train"
}
},
{
$group : {
"_id" : "$uid",
"travel": { $push: "$travel" }
}
}
]
)
结果:
{ _id: '1000002', travel: [ { vehicle: 'train', city: '深圳' } ] }
{ _id: '1000001', travel: [ { vehicle: 'train', city: '北京' }, { vehicle: 'train', city: '深圳' } ] }
分析:
unwind 可以将一个数组拆分,例如unwind的效果如下:
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
uid: '1000001',
name: 'zhangsan',
job: 'coder',
travel: { vehicle: 'train', city: '北京' } }
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
uid: '1000001',
name: 'zhangsan',
job: 'coder',
travel: { vehicle: 'plane', city: '上海' } }
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
uid: '1000001',
name: 'zhangsan',
job: 'coder',
travel: { vehicle: 'train', city: '深圳' } }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
uid: '1000002',
name: 'lisi',
job: 'coder',
travel: { vehicle: 'plane', city: '北京' } }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
uid: '1000002',
name: 'lisi',
job: 'coder',
travel: { vehicle: 'car', city: '上海' } }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
uid: '1000002',
name: 'lisi',
job: 'coder',
travel: { vehicle: 'train', city: '深圳' } }
然后通过match筛选出符合条件的数据
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
uid: '1000001',
name: 'zhangsan',
job: 'coder',
travel: { vehicle: 'train', city: '北京' } }
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
uid: '1000001',
name: 'zhangsan',
job: 'coder',
travel: { vehicle: 'train', city: '深圳' } }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
uid: '1000002',
name: 'lisi',
job: 'coder',
travel: { vehicle: 'train', city: '深圳' } }
最后通过group进行聚合,以_id为聚合依赖,合并相同_id的数据。
总结:
这种方法是能够达到过滤数组的要求,但是有一个问题,拆分数组比较简单,想要再合并起来就不容易了。group只能以某一个变量为基准聚合,其他变量都会丢失。比如最后的结果只保留了_id和travel,其他变量都丢失了。
方法二
方法二:使用$match过滤符合条件的根文档结果集,然后使用$project返回对应字段的同时,在travel数组中使用$filter进行内部过滤,返回最终结果集
db.getCollection('test').aggregate(
[
{
$match : { "job": "coder" }
},
{
$project: {
"uid": 1,
"name": 1,
"travel": {
$filter: {
input: "$travel",
as: "item",
cond: { $eq : ["$$item.vehicle","train"] }
}
}
}
}
]
)
结果分析:
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
uid: '1000001',
name: 'zhangsan',
travel: [ { vehicle: 'train', city: '北京' },{ vehicle: 'train', city: '深圳' } ] }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
uid: '1000002',
name: 'lisi',
travel: [ { vehicle: 'train', city: '深圳' } ] }
分析:
mongo中查询分为两种:普通查询和高级查询。高级查询包括聚合查询,用aggregate关键字实现。
MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
这里我们介绍一下聚合框架中常用的几个操作:
$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。$match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。$limit:用来限制MongoDB聚合管道返回的文档数。$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。$group:将集合中的文档分组,可用于统计结果。$sort:将输入文档排序后输出。$geoNear:输出接近某一地理位置的有序文档。
这里首先使用match过滤所有job=coder,然后使用project修改输出的结构。在project中使用了filter来过滤数组中的元素。
filter的定义如下:
根据指定条件选择要返回的数组的子集。返回仅包含与条件匹配的那些元素的数组。返回的元素按原始顺序。
$filter 具有以下语法:
{ $filter: { input: <array>, as: <string>, cond: <expression> } }
| 领域 | 规格 |
|---|---|
| input | 解析为数组的表达式 |
| as | 可选的。代表数组中每个单独元素的变量名称<u><font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">input</font></u>。如果未指定名称,则变量名称默认为<u><font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">this</font></u>。 |
| cond | 该表达式可解析为布尔值,该布尔值用于确定输出数组中是否应包含元素。该表达式<u><font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">input</font></u>使用在中指定的变量名称分别引用数组的每个元素<u><font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">as</font></u>。 |
https://mongodb.net.cn/manual/reference/operator/aggregation/filter/
在cond将vehicle=train的元素留下,排除其他元素。
总结:
这种方法可以完成查询目标,既可以过滤掉数组中的元素,也可以返回完整的文档。
方法三
方法三:
通过投影查询,先选择符合条件的记录,在通过使用投影操作符,需要返回的字段,以及排除特定的字段。
db.test.find(
{
job: "coder"
},
{
uid: 1,
name: 1,
travel: {
$filter: {
input: "$travel",
as: "item",
cond: { $eq : ["$$item.vehicle","train"] }
}
}
}
)
结果:
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
uid: '1000001',
name: 'zhangsan',
travel:
[ { vehicle: 'train', city: '北京' },
{ vehicle: 'train', city: '深圳' } ] }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
uid: '1000002',
name: 'lisi',
travel: [ { vehicle: 'train', city: '深圳' } ] }
分析:
什么是投影查询?
在MongoDB中,投影查询是一种查询操作,用于选择性地返回文档中的字段。通过使用投影操作符,我们可以指定需要返回的字段,以及是否要排除特定的字段。
投影查询语法如下所示:
db.collection.find({ <query> }, { <projection> })
其中, 是一个查询表达式,用于筛选满足条件的文档。 是一个可选参数,用于指定要返回的字段。
在projection中保留字段、排除字段、选择或排除数组中的特定元素。利用选择或排除数组中的特定元素的特性也可以达到目的。
例如:
如果我们只想返回每个文档中的第一个标签,我们可以这样做:
db.products.find({}, { tags: { $slice: 1 } })
在本篇中通过filter方法来过滤数组,保留符合条件的元素。
总结:
该方法能够完成查询目标,并且是一种简洁的实现,普通查询复杂度低,而且没有太多关键字的使用。
参考文档 :
https://geek-docs.com/mongodb/mongodb-questions/393_mongodb_mongo_query_with_projection.html
https://segmentfault.com/a/1190000016629733
https://mongodb.net.cn/manual/reference/operator/aggregation/filter/
https://blog.csdn.net/weixin_44009447/article/details/115479348
mongo对文档中数组进行过滤的三种方法的更多相关文章
- 【转载】取得系统中网卡MAC地址的三种方法
From:http://blog.csdn.net/zhangting1987/article/details/2732135 网卡地址这个概念有点混淆不清.因为实际上有两个地址,mac地址和物理地址 ...
- php数组合并有哪三种方法
php数组合并有哪三种方法 一.总结 一句话总结:array_merge():array_merge_recursive():‘+'号 $a = array('color'=>'red',5,6 ...
- Java中获取键盘输入值的三种方法
Java中获取键盘输入值的三种方法 Java程序开发过程中,需要从键盘获取输入值是常有的事,但Java它偏偏就没有像c语言给我们提供的scanf(),C++给我们提供的cin()获取键盘输入值 ...
- Android中设置文本颜色的三种方法
最近刚开始学web,发现好的颜色搭配可以让自己的网页更加美观, 中午不想做事,就无聊滴花了两个小时测试了所有颜色的编码,总结如下 新手没有什么吊炸天的技术,仅仅是一份辅助的文档,有兴趣的朋友可以收藏下 ...
- Linux中创建Daemon进程的三种方法
什么是daemon进程? Unix/Linux中的daemon进程类似于Windows中的后台服务进程,一直在后台运行运行,例如http服务进程nginx,ssh服务进程sshd等.注意,其英文拼写为 ...
- ZH奶酪:PHP中添加HTML代码的三种方法
php中添加HTML代码,就是php类型的文件中添加html代码~ 第一种是在HTML中加PHP. 大段大段的html代码中,在各个需要执行php的地方<?php .... ?> 比如 l ...
- HTML页面中插入CSS样式的三种方法
1. 外部样式 当样式需要应用于很多页面时,外部样式表将是理想的选择.在使用外部样式表的情况下,你可以通过改变一个文件来改变整个站点的外观.每个页面使用<link>标签链接到样式表. &l ...
- Java入门:Java中获取键盘输入值的三种方法
Java程序开发过程中,需要从键盘获取输入值是常有的事,但Java它偏偏就没有像c语言给我们提供的scanf(),C++给我们提供的cin()获取键盘输入值的现成函数!Java没有提供这样的函数也不代 ...
- Centos8(Liunx) 中安装PHP7.4 的三种方法和删除它的三种方法
编译安装 Centos8下PHP源码编译和通过yum安装的区别和以后的选择 其实这两种方法各有千秋: yum安装: 从yum安装来说吧,yum相当于是自动化帮你安装,你不用管软件的依赖关系,在yum安 ...
- 在VS中添加lib库的三种方法
注意: 1.每种方法也要复制相应的DLL文件到相应目录,或者设定DLL目录的位置,具体方法为:"Properties" -> "Configuration Prop ...
随机推荐
- 网络文件系统nfs服务端配置客户端权限时的demo例子
参考: https://www.cnblogs.com/devilmaycry812839668/p/15127755.html 由上面的参考资料我们可以知道在nfs服务端进行配置时对于客户端的权限设 ...
- 为什么使用服务器CPU运算Tensorflow、Pytorch代码会导致近百个逻辑核心的CPU使用率高达100%呢
2022年11月10日更新 本文所提问题与CPU的向量计算(simd)关系并不大,主要原因就是CPU多线程并行计算所导致的.不过CPU的SIMD导致CPU功耗大幅度上升并且导致CPU降频运行也确实会影 ...
- 【转载】 Ubuntu下使用VSCode的launch.json及tasks.json编写
版权声明:本文为CSDN博主「子木呀」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/qq_41687938/a ...
- 【转载】 深度学习——Xavier初始化方法
版权声明:本文为CSDN博主「shuzfan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/shuzfan/a ...
- 解锁GraphRag.Net的无限可能:手把手教你集成国产模型和本地模型
在上次的文章中,我们已经详细介绍了GraphRag的基本功能和使用方式.如果你还不熟悉,建议先阅读前面的文章 通过前两篇文章,相信你已经了解到GraphRag.Net目前只支持OpenAI规范的接口, ...
- Synology NAS GitLab 配置
安装 安装的时候会提示服务器名.root用户名等,这步服务器名千万不要写错,不然会登不上去,提示 502. root 密码 网上有很多说 root 密码怎么获取的,但是都不适用. 实际上是第一个访问 ...
- 微服务全链路跟踪:springcloud集成jaeger
微服务全链路跟踪:grpc集成zipkin 微服务全链路跟踪:grpc集成jaeger 微服务全链路跟踪:springcloud集成jaeger 微服务全链路跟踪:jaeger集成istio,并兼容u ...
- STM32开发踩大坑(技术总监出马救场)
代码中线进行spi初始化,再进行st7789的初始化.在st7789的初始化中,把spi初始化的配置信息pb15和pb13覆盖了,故数据传输不过去.当时st7789是直接拿样例代码过来用的,模拟spi ...
- 最常用集合 - arraylist详解
ArrayList介绍 ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现.除该类未实现同步外,其余跟Vector大致相同.每 ...
- vue自定义组件的点击事件失效
在vue开发过程中为了减少重复代码,很多时候都需要将重复的部分写成一个组件,方便调用.但是使用组件时很可能又会给该组件添加点击事件.如果直接这样写,事件则会失效: 正确写法应该是这样: