MongoDB 中的关联查询MongoDB : aggregate/lookup 对比 Mongoose : ref / populate

2023-12-23 03:38

本文主要是介绍MongoDB 中的关联查询MongoDB : aggregate/lookup 对比 Mongoose : ref / populate,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

摘自 :

MongoDB 中的关联查询

mongoosejs : Populate 官方文档

mongodb : lookup 官方文档

一、前言

数据库设计中数据之间的关联关系是极其常见的:一对一、一对多、多对多,作为 NoSQL 领头羊的 MongoDB 中常用做法无非「内嵌」和「引用」两种,

因为 Document 有 16MB 的大小限制[1]且「内嵌」不适合复杂的多对多关系,「引用」是用得更广泛的关联方式,
所以 MongoDB 官方称其为“Normalized Data Models”——标准化数据模型。

在这里插入图片描述

引用式的关联其实很简单,指文档与文档之间通过_id字段的引用来进行关联。
在需要 user 集合中“123xyz”的所有信息时只需要再多查两个表就可以得到。

而本文要阐述的重点就在于如何去多查这两个表——aggregatepopulate

二、剖析

1、aggregate + lookup

先来说说 aggregate 吧,为什么要先说它呢?
因为人家是 MongoDB 提供的功能——正儿八经血统纯正官方推荐啊🌚,
而且不得不提的是 aggregate 也是我刚开始接触 Node.js + MongoDB 就误打误撞使用到的业务核心技术,
使用其编写了不少现在正在和公司大佬一起重构的接口🤪……这个问题下文会讲到。

aggregate 聚合其实是 MongoDB 提供的比较大的功能模块了,
而关联多个集合需要用到的是$lookup,比如有作者集合 authors 和著作集合 books,作者与著作即为「一对多」的关联关系,使用引用式关联

在这里插入图片描述

因为技术栈是 Node.js + Express + Mongoose,以下代码示例也以此为基础,使用 express-generator 生成 demo 目录结构。

使用 aggregate 实现聚合查询作者 Zander 的基本信息及其所有著作信息:

router.get('/getAuthorInfo_a', async (req, res) => {let result = await Author.aggregate([{ // 操作的Model为Author$lookup: {from: "books", // 数据库中关联的集合名localField: "books", // author文档中关联的字段foreignField: "_id", // book文档中关联的字段as: "bookList" // 返回数据的字段名}}, {$match: { // 筛选条件"author": "Zander"}}]);res.json({status: 200,result: result})
});

返回数据:

{"status": 200,"result": [{"_id": "5dccfc3aa3fab06c89020c65","author": "Zander","age": 18,"bookList": [{"_id": "5dccfcb5a3fab06c89020c8d","name": "代码的弱点"},{"_id": "5dccfd30a3fab06c89020caa","name": "代码与六便士"},{"_id": "5dccfda6a3fab06c89020cc3","name": "代码失格"}]}]
}

aggregate 使用方法并不难,抛开结果先不谈,来看看 populate 的实现方式。

2、populate + ref

populateMongoose 中提供的方法,且 Mongoose单方言之populate()MongoDB$lookup更为强大🧐。那就拉出来溜溜呗🐎

首先,Mongoose 的一切始于 Schema[3],使用 populate 的重点也在于 Schema 中的设置:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const authorSchema = new Schema({"author": String,"age": Number,"books": [{type: Schema.Types.ObjectId,ref: 'Book' // 关联的Model}]
});
module.exports = mongoose.model("Author", authorSchema, "authors"); 
// 分别为Model名、Schema、数据库中集合名

在接口中使用populate():

router.get('/getAuthorInfo_p', async (req, res) => {let result = await Author.find({"author": "Zander"}).populate("books");res.json({status: 200,result: result})
})

返回数据:


{"status": 200,"result": [{"books": [{"_id": "5dccfcb5a3fab06c89020c8d","name": "代码的弱点"},{"_id": "5dccfd30a3fab06c89020caa","name": "代码与六便士"},{"_id": "5dccfda6a3fab06c89020cc3","name": "代码失格"}],"_id": "5dccfc3aa3fab06c89020c65","author": "Zander","age": 18}]
}

三、对比

  1. 灵活性

现在可以观察到的就是 aggregate 灵活的点在于可以更改关联查询后返回数据的 key(返回数据中的bookList),而 populate 返回数据的 key 只能是原来的字段名(返回数据中的books)。

值得一提的是 aggregate 更擅长在聚合管道中对数据进行二次处理,比如$unwind拆分、$group分组等等。

  1. 功能性

正向关联的话两者都能用,反向关联的话只能用lookup(但个人觉得正向的话还都是用populate,因为lookup写法较麻烦)

此外,还有一种情况:依旧是上面的数据,如果要根据著作 name 找到著作信息和作者信息,使用 aggregate$lookup只需要这样就做到了😏:


$lookup: {from: "authors",localField: "_id",foreignField: "books",as: "author"
}

然而 populate:“我太难了!”

是的,它做不到这种使用_id实现的反向关联查询,通俗点讲,Mongoose 不允许你这样写:

const bookSchma = new Schema({"_id": { // 不能这样写🙅‍♂️type: Schema.Types.ObjectId,ref: 'Author'},"name": String
});

如果你执意要尝试,那么这是你的下场🙃:

{"status": 200,"result": [{"_id": null,"name": "代码的弱点"}]
}

populate 是将一个集合的_id和另一个集合的非_id字段进行关联的,但是 Mongoose 4.5.0版本以后提供了与 aggregate 功能写法都非常类似的virtual()方法,这里不做详述了,有这个需求我用 aggregate 它不香么?🤨

  1. 代码简洁度

大概知道了它们的使用方法和适用场景后再来看看其它方面,比如为什么要重构之前完成的 aggregate 接口🥶。刚入职经验不足拿来别人的代码就依葫芦画瓢,画出来的「瓢」是这样的:

在这里插入图片描述

一方面是大量的回调函数,一方面是 aggregate 繁杂的写法,导致代码大量冗余,可读性也极差,现在重构后优雅的「葫芦」:

在这里插入图片描述

  1. 性能方面

看完了外表再说说内在——查询性能,populate 实际是DBRef[4]的引用方式,相当于多构造了一层查询。比如有10条数据,在find()查询到了主集合内的10条数据后会再进行populate()引用的额外10条数据的查询,性能也相对的大打折扣了。这里有位大佬对aggregate()和find()进行了性能上的对比,结论也显而易见——比 find 查询速度都快的 aggregate 比关联查询的 find + populate 定是有过之而无不及了。

四、总结

在这里插入图片描述

综合来看,aggregate 在多集合关联查询和对查询数据的二次处理方面更优,而 populate 更适合简单的正向关联关系且其形成的代码样式较优雅,可读性高而易于维护,性能方面的考究对日常开发中的普通应用来说则大可忽略不计。

技术的使用无不建立在需求和场景之上,不抱令守律,不因噎废食,知变通,知择优,毕竟技术只是工具,目的才是关键。

这篇关于MongoDB 中的关联查询MongoDB : aggregate/lookup 对比 Mongoose : ref / populate的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/526573

相关文章

MySQL 中的 JSON 查询案例详解

《MySQL中的JSON查询案例详解》:本文主要介绍MySQL的JSON查询的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 的 jsON 路径格式基本结构路径组件详解特殊语法元素实际示例简单路径复杂路径简写操作符注意MySQL 的 J

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

关于MongoDB图片URL存储异常问题以及解决

《关于MongoDB图片URL存储异常问题以及解决》:本文主要介绍关于MongoDB图片URL存储异常问题以及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录MongoDB图片URL存储异常问题项目场景问题描述原因分析解决方案预防措施js总结MongoDB图

MySQL索引的优化之LIKE模糊查询功能实现

《MySQL索引的优化之LIKE模糊查询功能实现》:本文主要介绍MySQL索引的优化之LIKE模糊查询功能实现,本文通过示例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一、前缀匹配优化二、后缀匹配优化三、中间匹配优化四、覆盖索引优化五、减少查询范围六、避免通配符开头七、使用外部搜索引擎八、分

SQL表间关联查询实例详解

《SQL表间关联查询实例详解》本文主要讲解SQL语句中常用的表间关联查询方式,包括:左连接(leftjoin)、右连接(rightjoin)、全连接(fulljoin)、内连接(innerjoin)、... 目录简介样例准备左外连接右外连接全外连接内连接交叉连接自然连接简介本文主要讲解SQL语句中常用的表

MySQL高级查询之JOIN、子查询、窗口函数实际案例

《MySQL高级查询之JOIN、子查询、窗口函数实际案例》:本文主要介绍MySQL高级查询之JOIN、子查询、窗口函数实际案例的相关资料,JOIN用于多表关联查询,子查询用于数据筛选和过滤,窗口函... 目录前言1. JOIN(连接查询)1.1 内连接(INNER JOIN)1.2 左连接(LEFT JOI

MySQL 中查询 VARCHAR 类型 JSON 数据的问题记录

《MySQL中查询VARCHAR类型JSON数据的问题记录》在数据库设计中,有时我们会将JSON数据存储在VARCHAR或TEXT类型字段中,本文将详细介绍如何在MySQL中有效查询存储为V... 目录一、问题背景二、mysql jsON 函数2.1 常用 JSON 函数三、查询示例3.1 基本查询3.2

MySQL中的交叉连接、自然连接和内连接查询详解

《MySQL中的交叉连接、自然连接和内连接查询详解》:本文主要介绍MySQL中的交叉连接、自然连接和内连接查询,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、引入二、交php叉连接(cross join)三、自然连接(naturalandroid join)四

mysql的基础语句和外键查询及其语句详解(推荐)

《mysql的基础语句和外键查询及其语句详解(推荐)》:本文主要介绍mysql的基础语句和外键查询及其语句详解(推荐),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录一、mysql 基础语句1. 数据库操作 创建数据库2. 表操作 创建表3. CRUD 操作二、外键

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元