Связь многие-ко-многим

Последнее обновление: 24.11.2023

Отношение многие-ко-многим предполагает, что сущность одного типа одновременно может иметь связи с множеством сущностей другого типа и наоборот. Например, один студент может посещать несколько университетских курсов. Соответственно один университетский курс может посещаться множеством студентов. То есть есть в данном сслучае имеем связь многие ко многим.

Физически на уровне базы данных обычно для создания подобной связи создается промежуточная таблица, через которую связываются две основные таблицы. В Sequelize поэтому для связи двух сущностей отношением многие-ко-многим нам надо задать промежуточную модель. Так, возьмем пример с курсами и студентами:

const Sequelize = require("sequelize"); 
// определяем объект Sequelize
const sequelize = new Sequelize({
  dialect: "sqlite",
  storage: "metanit.db",
  define: {
    timestamps: false
  }
});

const Student = sequelize.define("student", {
    id: {
      type: Sequelize.INTEGER,
      autoIncrement: true,
      primaryKey: true,
      allowNull: false
    },
    name: {
      type: Sequelize.STRING,
      allowNull: false
    }
});
  
const Course = sequelize.define("course", {
    id: {
      type: Sequelize.INTEGER,
      autoIncrement: true,
      primaryKey: true,
      allowNull: false
    },
    name: {
      type: Sequelize.STRING,
      allowNull: false
    }
});

// промежуточная сущность, которая связывает курс и студента
const Enrolment = sequelize.define("enrolment", {
    id: {
      type: Sequelize.INTEGER,
      autoIncrement: true,
      primaryKey: true,
      allowNull: false
    },
    grade: {					// оценка студента по данному курсу
      type: Sequelize.INTEGER,
      allowNull: false
    }
});

Student.belongsToMany(Course, {through: Enrolment});
Course.belongsToMany(Student, {through: Enrolment});


sequelize.sync({force:true}).then(()=>{

    console.log("Tables have been created");
}).catch(err=>console.log(err));

Здесь в качестве промежуточной сущности выступает модель Enrolment - по сути данные успеваемости определенного студента по определенному курсу. В этой модели можно определить различные свойства. Так, в данному случае определено свойство "grade", которое призвано хранить оценку студена по данному курсу. Аналогично в этой модели мы могли бы определить какие-нибудь атрибуты, которые бы связывали студента с курсом, например, дату поступления на данный курс, дату окончания и т.д.

Непосредственно для создания связи многие-ко-многим применяется метод belongsToMany(). Первый параметр метода - сущность, с которой надо установить связь. Второй параметр - объект конфигурации связи, который с помощью параметра through обязательно должен задавать промежуточную сущность, через которую будут связаны обе основные сущности.

В итоге при выполнении данного кода в базе данных SQLite будут созданы три таблицы с помощью следующих SQL-команд:

CREATE TABLE IF NOT EXISTS `students` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255) NOT NULL);
CREATE TABLE IF NOT EXISTS `courses` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255) NOT NULL);
CREATE TABLE IF NOT EXISTS `enrolments` (
    `id` INTEGER PRIMARY KEY AUTOINCREMENT, `grade` INTEGER NOT NULL, 
    `studentId` INTEGER REFERENCES `students` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 
    `courseId` INTEGER REFERENCES `courses` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 
    UNIQUE (`studentId`, `courseId`)
);

Добавление связанных данных

При установке связи многие ко многим модели могут использовать метод addИМЯ_МОДЕЛИ() для добавления объектов (например, student.addCourse() и course.addStudent()). Например, пусть у нас будет создано несколько объектов - студентов и курсов:

Course.create({ name: "JavaScript"});
Course.create({ name: "TypeScript"});
Course.create({ name: "Node.js"});

Student.create({ name: "Tom"});
Student.create({ name: "Bob"});
Student.create({ name: "Alice"});

Добавим студенту с именем Tom курс по JavaScript:

// получаем пользователя с именем Tom
Student.findOne({where: {name: "Tom"}})
.then(student=>{
    if(!student) return;
	
    // добавим Тому курс по JavaScript
    Course.findOne({where: {name: "JavaScript"}})
        .then(course=>{
            if(!course) return;
            student.addCourse(course, {through:{grade:1}});
    });
});

Первым параметром в student.addCourse() передается добавляемый курс. Вторым параметром устанавливается значение для столбца grade в таблице enrolments. В итоге данный метод будет выполнять sql-команду:

INSERT INTO `enrolments` (`id`,`grade`,`studentId`,`courseId`) VALUES (NULL,1,1,3);

Получение связанных данных

Для получения связанных данных у каждой из моделей, участвующих в связи, мы можем использовать метод getИМЯ_МОДЕЛИs(). Например, получим все курсы студента по имени Tom:

Student.findOne({where: {name: "Tom"}})
.then(student=>{
    if(!student) return;
    student.getCourses().then(courses=>{
        for(course of courses){
            console.log(course.name);
        }
    });
});

Однако в реальности в данном случае мы получаем не просто курс из таблицы courses, а сводные данные на основании таблицы enrolments - выполняемая sql-команда в данном случае будет выглядеть следующим образом:

SELECT `course`.`id`, `course`.`name`, `enrolment`.`id` AS `enrolment.id`, `enrolment`.`grade` AS `enrolment.grade`, `enrolment`.`studentId` AS `enrolment.studentId`, `enrolment`.`courseId` AS `enrolment.courseId` FROM `courses` AS `course` INNER JOIN `enrolments` AS `enrolment` ON
`course`.`id` = `enrolment`.`courseId` AND `enrolment`.`studentId` = 1;

То есть в данном случае мы сможем получить название и id курса, а также id и значение grade объекта Enrolment:

Student.findOne({where: {name: "Tom"}})
.then(student=>{
    if(!student) return;
    student.getCourses().then(courses=>{
        for(course of courses){
            console.log("course:", course.name, "grade:", course.enrolment.grade);
        }
    });
});

Удаление связанных данных

Для удаления связанных данных необходимо получить объект из промежуточной таблицы и удалить его. Например, удалим у студента по имени Tom курс JavaScript:

Student.findOne({where: {name: "Tom"}})
.then(student=>{
    if(!student) return;
    student.getCourses().then(courses=>{
        for(course of courses){
            if(course.name==="JavaScript") course.enrolment.destroy();
        }
    });
});
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850