Skip to content

Mongo 中的多表联查

表结构

blog

ts
export type BlogDocument = HydratedDocument<Blog>;

export type Music = {
  src: string;
  poster?: string;
  name: string;
  artist?: string;
  lyric?: string;
  album?: string;
};

@Schema()
export class Blog {
  content: string;

  likeNum?: number;

  address?: string;

  imageList?: string[];

  musicList?: Music[];

  createDate: string;

  createTime: string;
}

export const BlogSchema = SchemaFactory.createForClass(Blog);

comment

ts
export type CommenDocument = HydratedDocument<Comment>;

@Schema()
export class Comment {
  parentId?: string;

  parantName?: string;

  content: string;

  authorName: string;

  authorEmail: string;

  isOriginal: boolean;

  createDate: string;

  createTime: string;
}

export const CommentSchema = SchemaFactory.createForClass(Comment);

关联表

ts
export class Blog {
  @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: "Comment" }] })
  commentIds: Comment[];
}

export class Comment {
  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: "Blog" })
  blogId: Blog;
}

关联表操作

查找 blog 列表时,通常操作的结果如下:

json
{
  "_id": {
    "$oid": "6629cb98b40578481252ff68"
  },
  "content": "这是一篇博客",
  "likeNum": 10,
  "imageList": [],
  "musicList": [
    //...
  ],
  "createDate": "2024-04-25",
  "createTime": "2024-04-25 11:18:48",
  "commentIds": ["6629d4d69e4518a42aac99d9", "662a1b4078b25e11672b0f24"],
  "__v": 0
}

查找到的 commentsid 形式,如果需要将具体的映射到字段上,需要使用 populate 操作:

ts
this.blogModel
  .find()
  .sort({ createTime: -1 })
  .skip((page - 1) * size)
  .limit(size)
  .populate({ path: "commentIds", options: { strictPopulate: false } }) // 这一步是映射操作
  .exec();

// 也可以通过 select 字段选取想要的属性
// populate({ path: "commentIds", select "authorName, content", options: { strictPopulate: false } })

同理,查找某个 blog 的评论的操作如下:

ts
this.blogModel.findOne({ _id: blogId }).populate({
  path: "commentIds",
  options: {
    skip: (page - 1) * size,
    limit: size,
    sort: { createTime: -1 },
  },
});

console.log(blog.commentIds);

多重投影

假设有用户表,角色表和权限表,是 1:1:n 的关系

即通过 user.roleRef.permissions 的链路获取到用户的权限

我们可以通过多重投影的方式拿到

ts
const user: User = await this.userModel
  .findOne({ username })
  .populate({
    path: "roleRef",
    populate: { path: "permissions" },
  })
  .exec();

投影后做过滤

某张 Model 对另一个 Model 的引用,我这里将它称为 ref(refs), 例如 album 表上,封面字段 (cover) 引用了照片表的的 document, 写作 coverRef

在投影后,我们可能需要对 ref(refs) 进行属性的筛选或者分页

属性筛选:

typescript
const query = this.albumModel
  .find({}, { photos: 0 })
  .populate({ path: "coverRef", options: { select: { imageUrl: 1 } } });

分页:

typescript
const album = await this.albumModel
  .findOne({ _id })
  .populate("coverRef")
  .populate({
    path: "photos",
    options: {
      skip: 0,
      limit: 10,
      sort: { createTime: -1 }, // 按创建时间降序排列
    },
  });

参考

2025( )
今日 20.83%
本周 42.86%
本月 10.00%
本年 67.40%
Powered by Snowinlu | Copyright © 2024- | MIT License