首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

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

编程知识
2024年10月12日 09:56

前言

在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" : "深圳"
        }
    ]
}

想要实现数组的过滤有三种方法,包括:

  1. 聚合查询 使用$unwindtravel数组打散,获取结果集后用$match筛选符合条件的数据,最后使用$group进行聚合获取最终结果集
  2. 聚合查询 使用$match过滤符合条件的根文档结果集,然后使用$project返回对应字段的同时,在travel数组中使用$filter进行内部过滤,返回最终结果集
  3. 普通查询 先筛选记录,然后通过投影查询过滤数组

下面来分析这三种方法能否实现需求。

添加数据

假设有两条记录,每条记录是一个人的信息,包括姓名、职业、旅游过的城市。旅游过的城市是一个数组,包含城市的名字以及交通工具。

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的记录,过滤掉非目标记录。

方法一

方法一:使用$unwindtravel数组打散,获取结果集后用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 可选的。代表数组中每个单独元素的变量名称input。如果未指定名称,则变量名称默认为this
cond 该表达式可解析为布尔值,该布尔值用于确定输出数组中是否应包含元素。该表达式input使用在中指定的变量名称分别引用数组的每个元素as。

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

From:https://www.cnblogs.com/goldsunshine/p/18460043
本文地址: http://shuzixingkong.net/article/2492
0评论
提交 加载更多评论
其他文章 Nuxt.js 应用中的 ready 事件钩子详解
title: Nuxt.js 应用中的 ready 事件钩子详解 date: 2024/10/12 updated: 2024/10/12 author: cmdragon excerpt: ready 钩子是 Nuxt.js 中一个重要的生命周期事件,它在 Nuxt 实例初始化完成后被调用。当 N
Nuxt.js 应用中的 ready 事件钩子详解 Nuxt.js 应用中的 ready 事件钩子详解
再见,数据中台,理想还在路上
近日,Gartner发布了24年《中国数据分析及人工智能成熟度周期报告》,在成熟度曲线中声明“数据中台”已被淘汰。数据中台,这个曾被奉若圭臬,视为先进架构的标志性建筑,将就此将淡出历史舞台。 有些东西,在它真正消亡前,就已经被遗忘。 其实,早在几年前,国内技术圈已经不再热衷于数据中台概念,一位IT媒
再见,数据中台,理想还在路上
SpringBoot进阶教程(八十一)Spring Security自定义认证
在上一篇博文《SpringBoot进阶教程(八十)Spring Security》中,已经介绍了在Spring Security中如何基于formLogin认证、基于HttpBasic认证和自定义用户名和密码。这篇文章,我们将介绍自定义登录界面的登录验证方式。 v定义认证过程 系统源码 自定义认证的
SpringBoot进阶教程(八十一)Spring Security自定义认证 SpringBoot进阶教程(八十一)Spring Security自定义认证
WebAssembly 基础以及结合其他编程语言
WebAssembly 基础以及结合 C/C++、C#、Go、Python、Rust、TypeScript
封神台 SQL注入 靶场 (猫舍)手动注入
封神台 SQL注入 靶场 (猫舍)手动注入 靶场地址 http://pu2lh35s.ia.aqlab.cn/?id=1 使用脚本 可以直接使用sqlmap脚本 直接 对这个地址进行测试 不过这样实在是太没意思了 这里使用的是 sqlmap 二次开发的 sqlmapplus 脚本 sqlmap 也是
封神台 SQL注入 靶场 (猫舍)手动注入 封神台 SQL注入 靶场 (猫舍)手动注入 封神台 SQL注入 靶场 (猫舍)手动注入
推荐一款支持Vue3的管理系统模版:Vue-Vben-Admin
近年来,随着前端技术的飞速发展,各类后台管理系统框架层出不穷。Vue 作为热门的前端框架,也有许多优秀的后台模板涌现。而 Vue-Vben-Admin,凭借其高效、灵活的架构设计和完善的功能体系,成为了许多前端开发者的不二选择。其Github Star达到了24K之多,可见其受欢迎程度。本文将详细介
推荐一款支持Vue3的管理系统模版:Vue-Vben-Admin 推荐一款支持Vue3的管理系统模版:Vue-Vben-Admin
手撸二叉树——二叉查找树
二叉树是数据结构中非常重要的一种数据结构,它是树的一种,但是每个节点的子节点不能多余两个,可以是0,1,2个子节点,0个子节点代表没有子节点。常见的二叉树结构如下图所示: 每个节点的子节点不多于2个,其中3,4,5没有子节点,2有一个子节点,0,1都有两个子节点。 基础概念 根节点:树的其实节点,没
手撸二叉树——二叉查找树 手撸二叉树——二叉查找树 手撸二叉树——二叉查找树
C#轻松实现Modbus通信
1、前言 大家好!我是付工。前面给大家介绍了一系列关于RS485与Modbus的知识。 终于有人把RS485说清楚了终于有人把Modbus说明白了通透!终于把ModbusRTU弄明白了这样看来,ModbusTCP协议太简单了今天跟大家聊聊关于C#如何实现Modbus通信。 2、开源通信库 通信库是对
C#轻松实现Modbus通信 C#轻松实现Modbus通信