Express+MongoDB中的关联查询操作

前言

一对一、一对多、多对多,作为 NoSQL 领头羊的 MongoDB 中常用做法无非「内嵌」和「引用」两种,因为 Document 有 16MB 的大小限制且「内嵌」不适合复杂的多对多关系,「引用」是用得更广泛的关联方式,所以 MongoDB 官方称其为“Normalized Data Models”——标准化数据模型。

引用式的关联其实很简单,指文档与文档之间通过id字段的引用来进行关联,下图是在文章表中包含了分类和标签表中的id字段,作为外键存在

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5CwePe0-1665286430121)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d97dd5c43024466da90eda4727d4aa33~tplv-k3u1fbpfcp-zoom-1.image)]

这里是三张不同的表:文章、分类、标签,mongoose的id会自动生成

下面我们会根据这张图中的关系分别使用populate和aggregate的方式进行查询

模拟数据

首先新增分类和标签模型内容

// 创建分类模型
const CategorySchema = new mongoose.Schema({
  name: { type: String }
})
// 创建标签模型
const TagsSchema = new mongoose.Schema({
  name: { type: String }
})
// 新增分类
  await Category.insertMany([
     { name: 'nodejs' },
     { name: 'vuejs' },
   ])
  await Tags.insertMany([
     { name: 'JavaScript' },
     { name: 'vue' },
     { name: 'react' },
     { name: 'html' },
     { name: 'css' },
  ])

新增文章并模拟数据关联

await Article.insertMany([
    { title: '博客文章1', body: '内容1', cid: "634144ba02bb3f9e44b7b3ae", tid: "634144d3ec1d411217edf8f1" },
    { title: '博客文章2', body: '内容2', cid: "634144ba02bb3f9e44b7b3af", tid: ["634144d3ec1d411217edf8f2", "634148e9aa35256326ce8f6a"] }
  ])

关联查询

Populate

官方解释:

MongoDB在版本>= 3.2中有类似于联接的$查找聚合运算符。Mongoose有一个更强大的替代方案,称为populate(),它允许您引用其他集合中的文档。填充是自动用来自其他集合的文档替换文档中的指定路径的过程。我们可以填充单个文档、多个文档、一个普通对象、多个普通对象,或者从查询返回的所有对象

Mongoose的一切始于Schema,使用populate的重点也在于Schema中的设置

const ArticleSchema = new mongoose.Schema({
  title: { type: String },
  body: { type: String },
  author: { type: String },
  cid: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "Category"
  },
  tid: [
    {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Tags"
    }
  ]
})

这里是Article模型

下面在接口中使用populate()

router.get(`/api/article/article_list`, async (req, res) => {
  const result = await Article.find({}).populate('cid').populate('tid')
  res.json({
    code: CODE_SUCCESS,
    msg: "查询成功",
    data: {
      result
    }
  })
})

返回的结果数据:

{
    "code": 0,
    "msg": "查询成功",
    "data": {
        "result": [
            {
                "_id": "63414ae914a4ebfba8a14bf0",
                "title": "博客文章1",
                "body": "内容1",
                "cid": {
                    "_id": "634144ba02bb3f9e44b7b3ae",
                    "name": "nodejs",
                    "__v": 0
                },
                "tid": [
                    {
                        "_id": "634144d3ec1d411217edf8f1",
                        "name": "JavaScript",
                        "__v": 0
                    }
                ],
                "__v": 0
            },
            {
                "_id": "63414ae914a4ebfba8a14bf2",
                "title": "博客文章3",
                "body": "内容3",
                "cid": {
                    "_id": "634144ba02bb3f9e44b7b3af",
                    "name": "vuejs",
                    "__v": 0
                },
                "tid": [
                    {
                        "_id": "634144d3ec1d411217edf8f2",
                        "name": "vue",
                        "__v": 0
                    },
                    {
                        "_id": "634148e9aa35256326ce8f6a",
                        "name": "react",
                        "__v": 0
                    }
                ],
                "__v": 0
            }
        ]
    }
}

aggregate

作为MongoDB官方推荐聚合查询方式,而要关联多个集合需要使用$lookup,如果一篇文章可以有多个标签(tags),划分到多个分类(categories),作为一对多的关联关系

使用aggregate的话不再需要指向对应的模型

  cid: {
    type: mongoose.Schema.Types.ObjectId,
  },
  tid: [
    {
      type: mongoose.Schema.Types.ObjectId,
    }
  ]

进行聚合查询

router.get(`/api/article/article_list`, async (req, res) => {
  const result = await Article.aggregate([
    {
      $lookup: {
        from: 'categories', // 数据库中关联的集合名称
        localField: 'cid', // article模型中关联的字段
        foreignField: '_id', // 需要关联categories中的字段
        as: 'categoryList'  // 返回数据的别名字段
      },
    },
    {
      $lookup: {
        from: 'tags', // 数据库中关联的集合名称
        localField: 'tid', // article模型中关联的字段
        foreignField: '_id', // 需要关联tags中的字段
        as: 'tagList'  // 返回数据的别名字段
      },
    },
    {
      $match: {} // 这里是查询时的筛选条件,查询全部设置为空,或者不用设置
    }
  ])
  res.json({
    code: CODE_SUCCESS,
    msg: "查询成功",
    data: {
      result
    }
  })
})

返回数据结果

{
    "code": 0,
    "msg": "查询成功",
    "data": {
        "result": [
            {
                "_id": "63423143d78882b9d887224d",
                "title": "博客文章1",
                "body": "内容1",
                "cid": "634144ba02bb3f9e44b7b3ae",
                "tid": [
                    "634144d3ec1d411217edf8f1"
                ],
                "__v": 0,
                "categoryList": [
                    {
                        "_id": "634144ba02bb3f9e44b7b3ae",
                        "name": "nodejs",
                        "__v": 0
                    }
                ],
                "tagList": [
                    {
                        "_id": "634144d3ec1d411217edf8f1",
                        "name": "JavaScript",
                        "__v": 0
                    }
                ]
            },
            {
                "_id": "63423143d78882b9d887224f",
                "title": "博客文章2",
                "body": "内容2",
                "cid": "634144ba02bb3f9e44b7b3af",
                "tid": [
                    "634144d3ec1d411217edf8f2",
                    "634148e9aa35256326ce8f6a"
                ],
                "__v": 0,
                "categoryList": [
                    {
                        "_id": "634144ba02bb3f9e44b7b3af",
                        "name": "vuejs",
                        "__v": 0
                    }
                ],
                "tagList": [
                    {
                        "_id": "634148e9aa35256326ce8f6a",
                        "name": "react",
                        "__v": 0
                    },
                    {
                        "_id": "634144d3ec1d411217edf8f2",
                        "name": "vue",
                        "__v": 0
                    }
                ]
            }
        ]
    }
}

参考链接:

MongoDB 中的关联查询


个人博客已上线,欢迎来访~
传送门:夜雨炊烟

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜雨炊烟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值