mongoose关联查询技巧笔记
前言
数据库设计中数据之间的关联关系是极其常见的:一对一、一对多、多对多,作为 NoSQL 领头羊的 MongoDB 中常用做法无非「内嵌」和「引用」两种,因为 Document 有 16MB 的大小限制且「内嵌」不适合复杂的多对多关系,「引用」是用得更广泛的关联方式,所以 MongoDB 官方称其为“Normalized Data Models”——标准化数据模型。
引用式的关联其实很简单,指文档与文档之间通过_id字段的引用来进行关联。Mongoose 4.5.0版本以后提供了与 aggregate 功能写法都非常类似的virtual()方法,这里先不做对比,可查看另一篇笔记mongoose4.5之virtual虚拟值填充,本文要阐述的重点就在于如何去查这两个表通过aggregate 与 populate,并介绍4.5的aggregate的关联引用反向查询。
populate
先说说populate吧,首先,Mongoose 的一切始于 Schema,使用 populate 的重点也在于 Schema 中的设置:
1 | const mongoose = require('mongoose'); |
使用
1 | let result = await Author.find({ |
返回如下数据
1 | [ |
aggregate
使用 aggregate 实现聚合查询作者 Zander 的基本信息及其所有著作信息:
1 | let result = await Author.aggregate([{ // 操作的Model为Author |
返回数据:
1 | [ |
对比
1. 灵活性
现在可以观察到的就是 aggregate 灵活的点在于可以更改关联查询后返回数据的 key(返回数据中的bookList),而 populate 返回数据的 key 只能是原来的字段名(返回数据中的books)。值得一提的是 aggregate 更擅长在聚合管道中对数据进行二次处理,比如$unwind
拆分、$group
分组等等。
2. 功能性
此外,还有一种情况:依旧是上面的数据,如果要根据著作 name 找到著作信息和作者信息,使用 aggregate 的$lookup只需要这样就做到了😏:
1 | $lookup: { |
然而 populate:“我太难了!” 是的,它做不到这种使用_id实现的反向关联查询.
3. 性能方面
看完了外表再说说内在——查询性能,populate 实际是DBRef
[^4]的引用方式,相当于多构造了一层查询。比如有10条数据,在find()
查询到了主集合内的10条数据后会再进行populate()
引用的额外10条数据的查询,性能也相对的大打折扣了。这里有位大佬对aggregate()
和find()
进行了性能上的对比,结论也显而易见——比 find 查询速度都快的 aggregate 比关联查询的 find + populate 定是有过之而无不及了。
总结
aggregation | populate | |
---|---|---|
灵活性 | ⭐️⭐️⭐️⭐️⭐ | ⭐️ |
反向关联 | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️ |
功能性 | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ |
代码简洁度 | ⭐️ | ⭐️⭐️⭐️⭐️⭐️ |
查询性能 | ⭐️⭐️⭐️⭐️ | ⭐️⭐️ |
综合来看,aggregate 在多集合关联查询和对查询数据的二次处理方面更优,而 populate 更适合简单的正向关联关系且其形成的代码样式较优雅,可读性高而易于维护,性能方面的考究对日常开发中的普通应用来说则大可忽略不计。
关联技巧一:关联引用子查父
1 | $lookup: { |
也可通过 mongoose4.5之virtual虚拟值填充 快速获取父内容
关联技巧二:关联数组转对象
1 | const lookup = { |
1 | const project = { |