mongodb - 构建 mogodb 模式
问题描述
我在玩 MongoDB,想知道 SQL 模式如何与 MongoDB 相对应的最佳实践是什么。这是我到目前为止的表格/数据:
user
- ID
- 电子邮件
- 姓名
answer
- user_id (FK user.id)
- 标签
- 赞成票
repo
- ID
- 所有者
- 姓名
- 描述
- 星星
repo_tag
- repo_id(FK 到 repo.id)
- 标签
- is_language
- 百分比
repo_contrib
- repo_id(FK 到 repo.id)
- user_id(FK 到 user.id)
- lines_of_code
结构是这样的:
- 用户
- 答案(左外)
- repo_contrib(左外)
- 回购
- repo_tag
- 回购
注意:所有用户都至少有一个答案或一个 repo,但不一定必须同时拥有。
我怎么能把它放到一个 mongo 模式中?这会是一个“收藏”吗?或者这将是两个集合:一个用于用户,一个用于 repo;或者更多?
我的查询将是这样的:“用 tay [Python] 获得超过 2 个赞成票的答案或带有超过 2 个星的 [Python] 标签的 repo 的所有用户。
解决方案
让我把它分为几个步骤:
第 1 步 - MONGODB 和 MONGOOSE
MongoDB 是一个基于文档的数据库。集合中的每条记录都是一个文档,并且每个文档都应该是自包含的(它应该包含您在其中需要的所有信息)。
由于 MongoDB 是一个无关系数据库,您不能在集合之间创建关系,但您可以将一个集合文档的引用存储为另一个集合文档的属性。为了帮助您管理所有这些,有一个很棒的包叫做Mongoose
,它允许您为每个集合创建一个模型。定义模型后,Mongoose
将允许您轻松地对数据库进行查询。
第 2 步 - 定义模型
正如我们所说,文档应该是独立的,因此它们应该包含您需要的所有信息。根据您的示例,我们可以有两种方法:
方法一:
为关系数据库中的每个表创建一个集合。当您拥有包含大量数据的文档时,这是最佳实践,因为它是可扩展的。
方法 2:
创建 3 个集合 - 用户、答案和回购。因为repo_contrib
没有很多数据,所以可以将所有用户的贡献存储在一个 USERS 文档中。这样,当您获取用户文档时,您将在一个地方拥有所需的一切。这同样适用repo_tag
——我们可以将所有 repo 的标签存储在一个 REPOS 文档中。
方法 3:
创建 2 个集合 - USERS 和 REPOS。与方法 2 相同,但您也可以将所有用户添加answers
到 USERS 文档中。
推荐:
在这种情况下,我会使用 APPROACH 2,因为它不存储大数据,并且可以轻松地存储在 USERS 和 REPOS 文档中,没有问题repo_contrib
。repo_tag
此外,如果我们采用这种方法,它将使查询数据库变得更加容易。我没有选择选项 3 的原因是因为理论上用户可以有数千或数万个答案,并且它不会很好地扩展。
第 3 步 - 实施
注意:MongoDB 会自动分配_id
给每个文档,因此您在实现模型时不必定义id
属性。
关系数据库示例中的表可以映射到这样的集合(此实现适用于 APPROACH 2):
用户收藏:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var schema = new Schema({
email: { type: String, required: true, unique: true },
name: { type: String, required: true, unique: false },
contributions: [{
repo_id: { type: mongoose.Schema.Types.ObjectId, ref: 'REPOS' },
lines_of_code: { type: Numeric, ref: 'REPOS' }
}]
});
const Users = mongoose.model('USERS', schema);
module.exports = Users;
答案集合:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var schema = new Schema({
user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'USERS', required: true },
tag: { type: String, required: true, unique: false },
upvotes:{ type: Number, default: 0, unique: false }
});
const Answers = mongoose.model('ANSWERS', schema);
module.exports = Answers;
回购集合:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var schema = new Schema({
owner: { type: mongoose.Schema.Types.ObjectId, ref: 'USERS', required: true },
name: { type: String, required: true, unique: false },
description: { type: String, required: false, unique: false },
stars:{ type: Number, default: 0, unique: false },
tags: [{
name: { type: String, required: true, unique: false },
is_language: {type: Boolean, required: true, unique: false},
percentage:{ type: Number, default: 0, unique: false }
}]
});
const Repos = mongoose.model('REPOS', schema);
module.exports = Repos ;
第 4 步 - 人口和数据库查询
的最佳功能之一Mongoose
称为population
. 如果您将一个集合文档的引用存储为另一个集合文档的属性,则在执行数据库查询时,Mongoose
会将引用替换为实际文档。
示例 1:
让我们首先以您建议的第一个查询为例:Find all users with an Answer with tag [Python] with more than 2 upvotes
. 由于我们将user_id
ANSWERS 集合作为对来自 USERS 集合的文档的引用存储在 ANSWERS 集合中,这意味着我们可以只查询 ANSWERS 集合,并且在返回最终结果Mongoose
时将转到 USERS 集合并将引用替换为实际的用户文档。将执行此操作的数据库查询如下所示:
const ANSWERS = require('../models/answers');
ANSWERS.find({
"tag": "Python",
"upvotes": {
"$gt": 2
}
}).populate('user_id');
示例 2:
您建议的第二个查询是:Find all repos with the [Python] tag with more than two stars
。由于我们将所有 repo 的标签存储在一个数组中,我们只需要检查该数组是否包含name
字段等于的项目Python
,并且 repo 的stars
字段大于 2。将执行此操作的数据库查询如下所示:
const REPOS = require('../models/repos');
REPOS.find({
"tags.name": "Python",
"stars": {
"$gt": 2
}
})
推荐阅读
- c# - ''Newtonsoft.Json.Linq.JArray' 不包含 'Properties' 的定义'
- android - 如何使用 ViewModelProvider 获取扩展 AndroidViewModel 的自定义 ViewModel?
- docker - 挂载路径会在 Kubernetes 中创建一个新目录
- scala - 来自 spark shell 的 Hive 选择语句
- java - Spring Boot:在构造函数中加载属性文件并用作自动装配注释
- blockchain - 如何仅从保管库查询中获取数据
- javascript - 为什么我需要覆盖prototype.constructor 值?
- php - SQLSTATE [42S02]:未找到基表或视图:1146 表“fresh_start.event”不存在(SQL:从“事件”中选择计数(*)作为聚合)
- javascript - React/Redux,从模型调用方法抛出 TypeError
- ios - 如何在我的 UIPageViewController.swift 中添加另一个页面?