Atualizado: 02/01/2025

Relação Muitos-para-Muitos (Many-to-Many) com Sequelize no Node.js

Uma relação Muitos-para-Muitos implica que uma entidade de um tipo pode ter conexões simultâneas com várias entidades de outro tipo, e vice-versa. Por exemplo, um estudante pode estar matriculado em vários cursos universitários, assim como um curso universitário pode ser frequentado por vários estudantes. Nesse caso, temos uma relação de muitos para muitos.

Fisicamente, no nível do banco de dados, é comum criar uma tabela intermediária para estabelecer essa conexão entre as duas tabelas principais. No Sequelize, para criar uma relação muitos-para-muitos entre duas entidades, é necessário definir um modelo intermediário. Vamos considerar o exemplo com cursos e estudantes:

const Sequelize = require("sequelize"); 
// definindo o objeto Sequelize
const sequelize = new Sequelize({
    dialect: "sqlite",
    storage: "programicio.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
    }
});
    
// entidade intermediária que conecta curso e estudante
const Enrolment = sequelize.define("enrolment", {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        primaryKey: true,
        allowNull: false
    },
    grade: {                    // nota do estudante no curso
        type: Sequelize.INTEGER,
        allowNull: false
    }
});
    
Student.belongsToMany(Course, {through: Enrolment});
Course.belongsToMany(Student, {through: Enrolment});
    
sequelize.sync({force:true}).then(() => {
    console.log("Tabelas foram criadas");
}).catch(err => console.log(err));

Aqui, o modelo Enrolment atua como a entidade intermediária, essencialmente representando o desempenho de um determinado estudante em um determinado curso. Nesse modelo, podemos definir várias propriedades. Neste exemplo, foi definido o atributo grade, que armazena a nota do estudante no curso. Da mesma forma, poderíamos definir outros atributos que conectam o estudante ao curso, como data de matrícula, data de conclusão, entre outros.

Para criar a relação muitos-para-muitos, utiliza-se o método belongsToMany(). O primeiro parâmetro do método é a entidade com a qual se deseja estabelecer a relação. O segundo parâmetro é um objeto de configuração que, através do atributo through, especifica a entidade intermediária que conectará as duas entidades principais.

Como resultado, ao executar este código, três tabelas serão criadas no banco de dados SQLite por meio dos seguintes comandos 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`)
);

Adicionando Dados Relacionados

Ao estabelecer uma relação muitos-para-muitos, os modelos podem utilizar o método addNomeDoModelo() para adicionar objetos (por exemplo, student.addCourse() e course.addStudent()). Suponha que tenhamos criado alguns objetos de estudantes e cursos:

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"});

Agora, vamos adicionar o curso de JavaScript ao estudante chamado Tom:

// obtendo o estudante chamado Tom
Student.findOne({where: {name: "Tom"}})
.then(student => {
    if (!student) return;
        
    // adicionando o curso de JavaScript ao Tom
    Course.findOne({where: {name: "JavaScript"}})
        .then(course => {
            if (!course) return;
            student.addCourse(course, {through:{grade:1}});
    });
});

O primeiro parâmetro em student.addCourse() é o curso a ser adicionado. O segundo parâmetro define o valor da coluna grade na tabela enrolments. Esse método resultará na execução do comando SQL:

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

Obtendo Dados Relacionados

Para obter dados relacionados, cada um dos modelos envolvidos na relação pode usar o método getNomeDoModelo(). Por exemplo, para obter todos os cursos do estudante chamado Tom:

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

Na realidade, neste caso, não obtemos apenas os cursos da tabela courses, mas também dados consolidados da tabela enrolments. O comando SQL correspondente será:

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;

Ou seja, podemos obter o nome e o id do curso, bem como o id e a nota (grade) do objeto Enrolment

Removendo Dados Relacionados

Para remover dados relacionados, é necessário obter o objeto da tabela intermediária e excluí-lo. Por exemplo, vamos remover o curso de JavaScript do estudante chamado Tom:

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();
        }
    });
});
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com