Я пытаюсь получить данные, как показано ниже:
"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.info('Connected to the database.');
})
.catch((error) => {
logger.error('Unable to connect to the database.', error);
console.info(`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, заключается в том, что я боюсь случайно потерять свои данные в продакшене, если буду обновлять модели или добавлять новые. Поэтому я использовал equalize-cli . С его помощью я могу превратить свои модели в таблицы, запустив sequelize db:migrate.
Причина, по которой я явно определил атрибут и имя таблицы, заключается в том, что я хочу, чтобы они следовали соглашениям об именах MySQL: например, attendance_years и attendance_year_id. Но когда я запускаю вызовы к базе данных, я вижу множество псевдонимов имен в терминале:ttage_year_id какttanceYearId и т. д. Я думаю, что это может повлиять на производительность запросов, поэтому я рассмотрю возможность того, чтобы sequenceize полностью управлял соглашениями об именах.
@AmadouBeye Имеет ли смысл делать это для получения примерной структуры, которую я указал в начале поста? РЕЛИГИИ имеют много специальностей. Я думаю, что это проводной.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вам нужно определить ассоциацию в файле religions следующим образом рядом с ассоциацией Religion.hasMany(models.Student:
Religion.hasMany(models.Major, {
foreignKey: "religionId",
as: "major",
});
Там написано: SequelizeDatabaseError: Unknown column 'major.religionId' in 'field list'.
Как вы создавали таблицы? Какое правильное поле внешнего ключа в major–religions?
В основной таблице majoi_id и имя, а в таблице религий - религия_id и имя. У меня нет других полей.
В этом случае вам нужно добавить недостающие столбцы и внешние ключи, чтобы связать все эти таблицы.
Это имеет смысл? Это как должно быть? Спасибо.
Ну, вы не создаете таблицы, используя sync метод Sequelize, и вручную создаете все таблицы, тогда да. В противном случае все записи в этих таблицах вообще были бы связаны.
Спасибо, что связались со мной в Твиттере. Я очень ценю это. При этом давайте посмотрим, сможем ли мы ответить на ваш вопрос. Пожалуйста, позвольте мне немного поразмыслить, прежде чем перейти к решению, которое я хочу предоставить.
primaryKey и включили атрибут tableName в объект параметров определений вашей модели, что нормально, но на самом деле не нужно, и на самом деле может фактически мешать запросам движков продолжения, и в этом случае вы можете должны определять эти атрибуты везде, и это просто беспорядок. Sequelize генерирует атрибуты primaryKey и tableName по умолчанию — поэтому, если можете, максимально уменьшите количество ненужных определений — Узнайте, почему, из документации по выводу имен таблиц здесь. Если вы чувствуете необходимость иметь свой собственный ключ для моделей, рассмотрите возможность использования атрибута 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
}
Это избавляет вас от необходимости присваивать уникальные имена полям primaryKey, а также предотвращает ситуации, когда ключи могут иметь одинаковые значения. Это также дает вам дополнительный уникальный атрибут для использования в ваших запросах, чтобы гарантировать, что вы получите одну запись.
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;
Также стоит иметь в виду, что когда вы связываете объекты модели, sequenceize будет автоматически множить имя объекта в результатах в зависимости от отношения (т. е. если родительский объект имеет много дочернего объекта) и возвращает результаты как множество. например если Religion.hasMany(Major), результат вернет religion.majors = [/*an array of majors*/].
Religion и Major -в файле модели 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.jsconst { 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 имеет много Enrollments, 1 - NReligion имеет много Students, 1 - N,Student может быть много Enrollment (и, соответственно, Major), 1 - N
Таким образом, первым шагом было бы, имхо, выяснить, что является «родителем» для чего, чтобы узнать, как и где создавать правильные ассоциации. Однако это коренным образом изменит способ формирования вашего json-ответа, поскольку между некоторыми сущностями нет прямых отношений (например, Religion никак напрямую не связан с Major, кроме как через Student -> Enrollment -> затем Major). Таким образом, ответом будет что-то вроде религий[i].students[i].enrollments[i].majors[i]. И в этом случае прямая сортировка Major в порядке Religion будет чем-то, что вы должны сделать после получения всех религий и их вложенных объектов, сопоставления Major с Student и сортировки их, как вы хотите. Насколько я знаю, нет единого запроса (или комбинации вложенных запросов), который мог бы сделать это для вас в базе данных SQL без прямого (или даже косвенного) внешнего ключа. Оповещение о спойлере, вот где появляется ошибка продолжения от.В целом, я думаю, что наиболее эффективным способом моделирования базы данных будет что-то вроде этого
Изображение, показывающее дизайн БД
Итак, как бы мы это сделали? Нам нужно создать «сквозные» модели для зачисления на специальность, от специальности к посещаемости и от религии к специальности. *** Обновлять *** «Проходные» модели будут выглядеть примерно так:
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 с Major состоит в том, чтобы, в основном, выполнить ряд шагов с данными, которые у вас есть, т.е.
const db = require('../path/to/db.js');
const Enrollment = require('../path/to/model/Enrollment');
const Major = require('../path/to/model/Major');
const Student = require('../path/to/model/Student');
const Religion = require('../path/to/model/Religion');
const EnrollmentMajors = require('../path/to/model/EnrollmentMajors');
const ReligionMajors = require('../path/to/model/ReligionMajors');
try{
const studentEnrollment = await Enrollment.create(
{
studentId: studentIdFromReq,
attendanceYearId: attendanceYearIdFromRequest,
}
);
if (studenEnrollment){
// associate the Enrollment with the Major if you have the Major id
const studentEnrolledMajor = await EnrollmentMajors.create(
{
enrollmentId: studentEnrollment.id,
majorId: majorId
}
)
// Then, get the students' Religion Id, and associate with Major
const studentWithReligion = await Student.findByPK(studentIdFromReq,
{include: [Religion]}
)
const religionMajorAssociation = await ReligionMajors.findOrCreate(
{
religionId: studentWithReligion.religion.id, // or student.religionId
majorId: majorId
}
)
/* we use findOrCreate to avoid making duplicate religion-major assocs */
if (religionMajorAssociation){
// The association was created successfully, so you can do whatever else
}
}
} catch(err){
console.info(err)
}
Обратите внимание, что я поместил код в блок try-catch. Как правило, это хорошая практика, поэтому вы можете легко увидеть, какие ошибки может вызвать сиквелизация (если они есть)...
Бесконечно благодарен. Я ценю ваши усилия. Я не ожидал, что много. Вы даете мне много ценной информации. Спасибо. Я обновил вопрос и добавил, как я думаю, как я его реализовал.
Как бы вы их связали? 1 к 1? н к н? от 1 до н?