我正在尝试获取如下数据:
"data": {
"religions": {
"Major1Name": {
"AttendanceYear1Name": {
"student": [ {...}, {...}, {...} ]
},
"AttendanceYear2Name": {
"student": [ {...}, {...}, {...} ]
}
},
"Major2Name": {
"AttendanceYear1Name": {
"student": [ {...}, {...}, {...} ]
},
"AttendanceYear2Name": {
"student": [ {...}, {...}, {...} ]
}
}
}
}
我知道如何为例如建立基本级别的关联。学生和专业。但在我的数据库知识中,我不知道如何与 religions
和 majors
以及 Sequelize
相关联。请帮忙。
我有以下表格:
下面是我的模型。
专业
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Major extends Model {
static associate(models) {
Major.hasMany(models.Enrollment, {
foreignKey: 'majorId',
as: 'major',
});
}
}
Major.init(
{
majorId: {
allowNull: false,
autoIncrement: true,
field: 'major_id',
primaryKey: true,
type: DataTypes.INTEGER,
},
{ ... }
},
{
sequelize,
modelName: 'Major',
tableName: 'majors',
}
);
return Major;
};
出勤_年
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class AttendanceYear extends Model {
static associate(models) {
AttendanceYear.hasMany(models.Enrollment, {
as: "enrollments",
foreignKey: "attendance_year_id",
});
}
}
AttendanceYear.init(
{
attendanceYearId: {
allowNull: false,
autoIncrement: true,
field: "attendance_year_id",
primaryKey: true,
type: DataTypes.INTEGER,
},
{ ... }
},
{
sequelize,
modelName: "AttendanceYear",
tableName: "attendance_years",
}
);
return AttendanceYear;
};
宗教
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Religion extends Model {
static associate(models) {
Religion.hasMany(models.Student, {
foreignKey: "religionId",
as: "student",
});
}
}
Religion.init(
{
religionId: {
allowNull: false,
autoIncrement: true,
field: "religion_id",
primaryKey: true,
type: DataTypes.INTEGER,
},
{ ... }
},
{
sequelize,
modelName: "Religion",
tableName: "religions",
}
);
return Religion;
};
学生
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Student extends Model {
static associate(models) {
Student.belongsTo(models.Religion, {
foreignKey: 'religionId',
as: 'religion',
targetKey: 'religionId',
});
Student.belongsTo(models.Enrollment, {
foreignKey: 'studentId',
as: 'enrollment',
});
}
}
Student.init(
{
studentId: {
allowNull: false,
autoIncrement: true,
field: 'student_id',
primaryKey: true,
type: DataTypes.INTEGER,
},
name: {
allowNull: false,
field: 'name_en',
type: DataTypes.STRING(50),
},
religionId: {
allowNull: false,
field: 'religion_id',
references: {
model: 'religons',
key: 'religion_id',
},
type: DataTypes.INTEGER,
},
},
{
sequelize,
modelName: 'Student',
tableName: 'students',
}
);
return Student;
};
和注册
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Enrollment extends Model {
static associate(models) {
Enrollment.belongsTo(models.Major, {
foreignKey: 'majorId',
as: 'major',
});
Enrollment.belongsTo(models.Student, {
foreignKey: 'studentId',
as: 'student',
});
Enrollment.belongsTo(models.AttendanceYear, {
foreignKey: 'attendanceYearId',
as: 'attendanceYear',
});
}
}
Enrollment.init(
{
enrollmentId: {
allowNull: false,
autoIncrement: true,
field: 'enrollment_id',
primaryKey: true,
type: DataTypes.INTEGER,
},
majorId: {
allowNull: false,
field: 'major_id',
onDelete: 'NO ACTION',
onUpdate: 'CASCADE',
references: {
model: 'majors',
key: 'major_id',
},
type: DataTypes.INTEGER,
},
studentId: {
allowNull: false,
field: 'student_id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'students',
key: 'student_id',
},
type: DataTypes.INTEGER,
},
attendanceYearId: {
allowNull: false,
field: 'attendance_year_id',
onDelete: 'NO ACTION',
onUpdate: 'CASCADE',
references: {
model: 'attendance_years',
key: 'attendance_year_id',
},
type: DataTypes.INTEGER,
},
},
{
sequelize,
modelName: 'Enrollment',
tableName: 'enrollments',
}
);
return Enrollment;
};
我做过和没做过的事情
const religions = await models.Religion.findAll({
where: { religionId: req.params.religionId },
include: [
{
model: models.Major,
as: 'major',
include: [
{
model: models.AttendanceYear,
as: 'attendanceYear',
include: [
{
model: models.Student,
as: 'student',
attributes: ['studentId', 'nameMm', 'nameEn', 'nrc'],
include: [
{
model: models.Parent,
as: 'parent',
attributes: ['fatherNameMm', 'fatherNameEn', 'fatherNrc'],
},
{
model: models.Enrollment,
as: 'enrollment',
attributes: ['rollNo'],
where: {
academicYearId: req.params.academicYearId,
},
},
],
},
],
},
],
},
],
});
错误
SequelizeEagerLoadingError: Major is not associated to Religion!
更新
我在这个结构 src/database/models/
中有以下模型(将是数据库中的表)文件:
整个结构是:
database/migrations/....js
database/models/....js
database/seeders/....js
我在 index.js
目录中有一个 models/
文件,如下所示:
'use strict';
const config = require('../../config/config');
const fs = require('fs');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const basename = path.basename(__filename);
const db = {};
const logger = require('../../startup/logger')();
const ENV = config[process.env.NODE_ENV];
let sequelize;
sequelize = new Sequelize(ENV.database, ENV.username, ENV.password, {
dialect: 'mysql',
host: ENV.host,
define: {
charset: 'utf8',
collate: 'utf8_general_ci',
timestamps: false, // omit createdAt and updatedAt
},
});
sequelize
.authenticate()
.then(() => {
// logger.info('Connected to the database.');
console.log('Connected to the database.');
})
.catch((error) => {
logger.error('Unable to connect to the database.', error);
console.log(`Unable to connect to the database.`, error);
process.exit(1);
});
fs.readdirSync(__dirname)
.filter((file) => {
return (
file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
);
})
.forEach((file) => {
const model = require(path.join(__dirname, file))(sequelize, DataTypes);
db[model.name] = model;
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
module.exports = db;
通过该实现,我不需要在模型文件中导入所需的模型以及路由处理程序,只需要以下行。
const models = require('../database/models');
/** I can easily get model instance by calling models.Student to get Student model. **/
我不使用 sync
方法的原因是,如果我更新模型或添加新模型,我害怕在生产中意外丢失我的数据。因此,我使用了 sequelize-cli
。有了它,我可以通过运行 sequelize db:migrate
将我的模型变成表格。
我明确定义属性和表名的原因是我希望它们遵循 MySQL 命名约定:例如 attendance_years
和 attendance_year_id
。但是当我运行对数据库的调用时,我在终端中看到了很多命名别名:attachment_year_id 为attachmentYearId 等。我认为这可能会影响查询性能,因此,我会考虑让 sequelize 完全管理命名约定。
答案 0 :(得分:2)
您需要在 religions
关联旁边的 Religion.hasMany(models.Student
文件中定义一个关联:
Religion.hasMany(models.Major, {
foreignKey: "religionId",
as: "major",
});
答案 1 :(得分:2)
感谢您在 Twitter 上与我联系。对此,我真的非常感激。话虽如此,让我们看看我们是否可以回答您的问题。在找到我希望提供的解决方案之前,请允许我稍微了解一下。
primaryKey
属性并在模型定义的选项对象中包含 tableName
属性,这是可以的,但确实没有必要,实际上可能会干扰续集引擎的查询,在这种情况下,您可能必须在任何地方定义这些属性,这简直是一团糟。 Sequelize 默认生成 primaryKey
属性和 tableName
- 因此,如果可以,请尽可能减少不必要的定义 - See why from the docs on table name inference here。如果您确实觉得需要为模型使用自己的自定义键,请考虑使用 UUID 属性,就像这样。// Custom UUID attribute seperate from the model id but also generated by sequelize.
studentUUID: {
type: DataTypes.UUID,
defaultValue: Sequelize.UUIDV4 // will generate a UUID for every created instance
}
这既省去了必须唯一命名主键字段的麻烦,也可以避免键可能具有相似值的情况。它还为您提供了一个额外的独特属性,可在您的查询中使用以确保您获得单个记录。
const { Model, Sequelize, Datatypes } = require('sequelize');
const db = require('../path/to/sequelizeConnectionInstance');
// Let's say we want to associate Religion model to Major model in a 1 - N relationship;
// To do that, we import the Major model
const Major = require('./Major');
class Religion extends Model { /* Yes, it's an empty object and that's okay */ }
Religion.init({
// Model attributes are defined here
name: {
type: DataTypes.STRING,
allowNull: false
},
founder: {
type: DataTypes.STRING
// allowNull defaults to true
},
{...}
}, {
// Other model options go here, but you rarely need more than the following 2
sequelize: db, // We need to pass the connection instance
modelName: 'religion' // I use small letters to avoid ambiguity. you'll see why in a bit
// No table name attribute is required. the table "religions" is automatically created
});
// The relationship(s) is/are then defined below
Religion.hasMany(Major);
Major.belongsTo(Religion); // The Major model must have a religionId attribute
/*
* Sequelize will automagically associate Major to Religion even without the FK being
* explicitly described in the association. What sequelize does is find the `modelName+id`
* attribute in the associated model. i.e. if `Foo.hasMany(Bar)` and `Bar.belongsTo(Foo)`, * sequelize will look for the `FooId` property in the Bar model, unless you specifiy
* otherwise. Also, the convention I use (and what I've found to work) is to import
* 'child' models to 'parent' model definitions to make
* associations.
*/
// THEN we export the model
modules.export = Religion;
另外值得记住的是,当你关联模型实体时,sequelize 会自动将结果中实体的名称复数,这取决于关系(即父实体是否有许多子实体),并返回结果作为数组。例如如果 Religion.hasMany(Major)
,结果将返回 religion.majors = [/*an array of majors*/]
。
Religion
和 Major
模型之间存在 1 - N 关系 -在 Major.js
模型文件中,您可以像这样指定宗教 FK
class Major extends Model {}
Major.init(
{
religionId: {
type: Datatypes.INTEGER,
allowNull: true, // set to false if the value is compulsory
// that's all folks. no other details required.
},
{/* ...otherAttributes */}
},
{/* ...options, etc */}
)
module.exports = Major;
然后在Religion.js
const Major = require('./Major');
Religion.init(
{
// just declare religions OWN attributes as usual
{/* ...religionObjectAttributes */}
},
{/* ...options, etc */}
)
Religion.hasMany(Major, {
foreignKey: 'religionId',
onDelete: 'NO ACTION', // what happens when you delete Major ?
onUpdate: 'CASCADE',
})
Major.belongsTo(Religion, {
foreignKey: 'religionId',
})
module.exports = Religion;
附带说明,您通常不必在关联中包含 onDelete
和 onUpdate
属性,因为默认值非常适合大多数用例。同样值得注意的是,您可以有多个关系,在这种情况下您可以使用别名。但这似乎与您的问题无关(至少从一开始),但仍然值得注意且非常有用。
您需要做的第一件事就是准确定义实体之间的关系结构。从 data
对象来看,在我看来就像
religions: [ // array of religions since your'e fetching multiple
{
id: 1, // the religion Id
name: string, // name of religion or whatever
/*... any other religion attributes */
majors: [ // array since each religion has multiple majors
{
id: 1, // the major Id
name: string, // the name of the major or whatever
/*... any other major attributes */
attendanceYears: [ // array since each major has multipl
{
id: 1, // the first attendanceYear id
name: string, // name of first attendanceYear
/*... any other attendanceYear attributes */
students: [ // array since ...
{
id: 1, // student id
name: string, // student name
/*... any other student attributes */
},
{
id: 2, // id of second student
name: string, // 2nd student name
/*... any other student attributes */
},
{
id: 3, // id of 3rd student
name: string, // 3rd student name
/*... any other student attributes */
},
]
},
{
id: 2, // the second attendanceYear id
name: string, // name of second attendanceYear
/*... other attributes of 2nd attendance year */
students: [
{
id: 4, // student id
name: string, // student name
/*... any other student attributes */
},
{
id: 5, // id of second student
name: string, // 2nd student name
/*... any other student attributes */
},
{
id: 6, // id of 3rd student
name: string, // 3rd student name
/*... any other student attributes */
},
]
}
]
},
{/*... this is another major instance in majors array */}
]
},
{/*... this is another religion instance in religions array*/}
]
好的。我不确定这是否是你想要的,但除了你给出的例子,这就是我正在使用的。对于代码,首先,一些配置将帮助您完成
db.js
const { Sequelize } = require('sequelize');
module.exports = new Sequelize('dbName', 'dbUsername', 'dbPassword', {
host: 'localhost',
dialect: 'mysql', // or whatever dialect you're using
});
我现在把它放在这里,只是为了清楚我在其他地方使用 db
变量时所指的是什么。然后我们创建模型
Religion.js
const { Model, Sequelize, DataTypes } = require('sequelize');
const db = require('../path/to/db.js');
// import any models to associate
const Student = require('./Student');
class Religion extends Model {}
Religion.init(
{
/* religion only attrs, let sequelize generate id*/
},
{
sequelize: db,
modelName: 'religion'
}
)
// make association
Religion.hasMany(Student);
Student.belongsTo(Religion);
module.exports = Religion;
Major.js
const { Model, Sequelize, DataTypes } = require('sequelize');
const db = require('../path/to/db.js');
// import any models to associate
const Enrollment = require('./Enrollment');
class Major extends Model {}
Major.init(
{
/* major only attrs, let sequelize generate id*/
},
{
sequelize: db,
modelName: 'major'
}
)
Major.hasMany(Enrollment)
Enrollment.belongsTo(Major);
module.exports = Major;
Student.js
const { Model, Sequelize, DataTypes } = require('sequelize');
const db = require('../path/to/db.js');
const Enrollment = require('./Enrollment');
class Student extends Model {}
Student.init(
{
religionId: {
type: DataTypes.INTEGER,
},
/* other student attrs, let sequelize generate id attr */
},
{
sequelize: db,
modelName: 'student'
}
)
Student.hasMany(Enrollment);
Enrollment.belongsTo(Student);
module.exports = Student;
Enrollment.js
const { Model, Sequelize, DataTypes } = require('sequelize');
const db = require('../path/to/db.js');
class Enrollment extends Model {}
Enrollment.init(
{
attendanceYearId: {
type: DataTypes.INTEGER, // FK for attendanceYear
},
studentId: {
type: DataTypes.INTEGER, // FK for student
},
majorId: {
type: DataTypes.INTEGER, // FK for major
},
/* other 'Major' attrs, let sequelize generate id attr */
},
{
sequelize: db,
modelName: 'enrollment'
}
)
module.exports = Enrollment;
AttendanceYear.js
const { Model, Sequelize, DataTypes } = require('sequelize');
const db = require('../path/to/db.js');
const Enrollment = require('./Enrollment');
class AttendanceYear extends Model {}
AttendanceYear.init(
{
/* attendanceYear attrs, let sequelize generate id attr */
},
{
sequelize: db,
modelName: 'attendanceYear'
}
)
AttendanceYear.hasMany(Enrollment);
Enrollment.belongsTo(AttendanceYear);
module.exports = AttendanceYear;
这样,您的所有实体都已设置为以您请求的形状获取数据。例如(在函数中使用)
someOtherFile.js
// First import all the models you may wish to use i.e.
const db = require('../path/to/db.js');
const Religion = require('../path/to/models/Religion');
const Major = require('../path/to/models/Major');
const AttendanceYear = require('../path/to/models/AttendanceYear');
const Student = require('../path/to/models/Student');
const Enrollment = require('../path/to/models/Enrollment');
// Uncomment the line below to update db structure if model changes are made
// db.sync({alter: true})
/* query function starts here */
const religions = await Religion.findAndCountAll({
// If you want all religions then you don't need a where object
// Also "where: {id: someValue}" should get only 1 result
include: [{model: Major, include: [{ model: Enrollment, include:[AttendanceYear, Student]}]}]
})
值得注意的是,如果您要使用它的主键搜索某些内容,那么 .findByPK(idValueOrVariable)
对此要好得多,您还可以传入包含和其他选项等。
话虽如此,但愿这能说明续集的工作原理以及如何解决问题;但是,我觉得这不是您想要的,如果不是,那么这至少为我提出的第二个解决方案奠定了基础。
Major
都有许多Enrollment
,反之亦然,N - N(因为一个学生可能在同一个注册中有多个专业)AttendanceYear
有许多 Enrollment
,1 - NReligion
有许多 Student
,1 - N,Student
可以有多个 Enrollment
(扩展为 Major
),1 - N
因此,第一步就是,恕我直言,弄清楚哪个是“父母”,以了解如何以及在何处进行正确的关联。但是,这将从根本上改变您的响应 json 的形成方式,因为某些实体之间没有直接关系(例如,Religion
与 Major
没有任何直接关系,除了通过 {{ 1}} -> Student
-> 然后是 Enrollment
)。因此,响应将类似于宗教[i].students[i].enrollments[i].majors[i]。在这种情况下,直接按 Major
的顺序对 Major
进行排序将是您 获取所有宗教及其嵌套对象并映射 {{ 1}} 和 Religion
并根据需要对它们进行排序。据我所知,没有单个查询(或嵌套查询的组合)可以在没有直接(甚至间接)外键的 SQL 数据库中为您执行此操作 - 剧透警报,这就是续集错误即将到来的地方来自。总的来说,我认为最有效的数据库建模方法是这样的
那么,我们将如何做到这一点?我们需要创建专业招生、专业到出勤、宗教到专业的“直通”模式。 *** 更新 *** “通过”模型看起来像这样:
Major
Student
ReligionMajors
const { Model, Sequelize, DataTypes } = require('sequelize');
const db = require('../path/to/db.js');
// import any models to associate
const Religion = require('./Religion');
const Major = require('./Major');
class ReligionMajors extends Model {}
ReligionMajors.init({
religionId: {
type: DataTypes.INTEGER,
references: { // You don't need to include this, just showing for reference
model: Religion,
key: 'id'
}
},
majorId: {
type: DataTypes.INTEGER,
references: { // again you don't need this, just showing for reference
model: Major,
key: 'id'
}
}
});
Religion.belongsToMany(Major, { through: ReligionMajors });
Major.belongsToMany(Religion, { through: ReligionMajors});
EnrollmentMajors
const { Model, Sequelize, DataTypes } = require('sequelize');
const db = require('../path/to/db.js');
// import any models to associate
const Enrollment = require('./Enrollment');
const Major = require('./Major');
class EnrollmentMajors extends Model {}
EnrollmentMajors.init({
enrolmentId: {
type: DataTypes.INTEGER,
},
majorId: {
type: DataTypes.INTEGER,
}
});
Enrollment.belongsToMany(Major, { through: EnrollmentMajors });
Major.belongsToMany(Enrollment, { through: EnrollmentMajors});
这个问题的棘手之处在于,您可能必须开始考虑何时以及如何将这些关联记录在案。此外,这会将 AttendanceYearMajors
和 const { Model, Sequelize, DataTypes } = require('sequelize');
const db = require('../path/to/db.js');
// import any models to associate
const AttendanceYear = require('./AttendanceYear');
const Major = require('./Major');
class AttendanceYearMajors extends Model {}
AttendanceYearMajors.init({
attendanceYearId: {
type: DataTypes.INTEGER,
},
majorId: {
type: DataTypes.INTEGER,
}
});
AttendanceYear.belongsToMany(Major, { through: AttendanceYearMajors });
Major.belongsToMany(AttendanceYear, { through: AttendanceYearMajors});
模型之间的关系更改为多对多关系,但这没关系。
正如我之前所说,我们现在可以做的是弄清楚何时以及如何在“通过”模型中创建记录以创建我们需要的关联。
进行 Major
到 Enrollments
关联的一种方法是,基本上使用您拥有的数据执行一系列步骤,即
Religion
请注意,我将代码放在 try-catch 块中。这通常是一个很好的做法,因此您可以轻松查看 sequelize 可能抛出的任何错误(如果有)...