JOINs com Sequelize, MySQL e Node.Js — parte 1

Edu Marcelino
7 min readMay 27, 2020

--

sequelize.query

Quem está acostumado com queries SQL e se depara com esse método do sequelize deve sentir um sorriso de orelha à orelha. Afinal se aproveitar das chamadas raw queries é uma mão na roda em casos em que o sequelize não suporte nativamente uma operação ou mesmo quando você ainda não aprendeu a fazer do modo nativo entregue pela ferramenta.

Se você está neste último caso, as próximas dicas são para você.

Disclaimer e agradecimento

Os conceitos demonstrados foram baseados no poster criado para mySQL por Steve Stedman. Deixo o link abaixo para quem se interessar

http://stevestedman.com/2015/03/mysql-join-types-poster/

Portanto vamos tentar reproduzir as ilustrações do poster no Sequelize, e exporar os muitos tipos de joins.

Configuração básica

Parto do princípio que você já tem em mãos Sequelize, Node.js e uma base de dados SQL, tudos instalados e prontos para os estudos.

Utilizarei três tabelas de exemplo:

Tabela 1: movies

Populada com 584 registros de filmes com as colunas abaixo com os seguintes campos:

name: Nome do filme
categoryId: Chave estrangeira para a tabela com as categorias de filme (gênero), como ação, comédia, drama, etc.
parentalRatingId: Chave estrangeira para a tabela de classificação parental, ou seja, se o filme é livre ou se existe classificação de idade.
directorName: Nome do diretor
dubLeg
: Informa se os filmes possuem versão legendada

Os dados não representam a realidade. Servem apenas para estudo.

Tabela 2: category

Tabela 3: parental

MODELS

Aqui criamos um model para cada tabela em um arquivo de models.

modules.js

Observem que utilizamos uma associação chamada belongsTo. Quer dizer que criamos uma relação de chaves estrangeiras entre as tabelas.
Então, no exemplo abaixo a tabela movies pertence à tabela categoria através do campo categoryId. Ou seja, a tabela ‘dona’ da informação “categorias” é a tabela category e nela existe a chave primária (primary key) no campo id.
A tabela movies utiliza desta informação em seu campo categoryId. Desta forma os dois campos, movies.categoryId e category.id, possuem um relacionamento belongsTo.

movies.belongsTo(category, { foreignKey: ‘categoryId’, allowNull: true });

O comando allowNull informa ao banco de dados para que permita a criação de um registro em movies com o campo categoryId nulo (vazio).

Mantenha seus models em arquivos separados para uma boa organização, e exporte-os para estarem disponíveis em outro arquivo .js

module.exports = { movies, category, parental }

CRIANDO O DATABASE E TABELAS

Crie um database no seu banco mySQL. Você pode fazer isso de várias formas. A que eu acho mais simples é utilizando a lib do seu banco. No meu caso, uso o MySQL então basta instalar o mysql2

npm install mysql2

Agora chamamos o mysql2 e podemos criar uma nova database:

const mysql2 = require(“mysql2”)
const connectionSql = mysql2.createConnection({
host: host,
user: user,
password: password
});

Com a conexão pronta, é só utilizar o comando.
Meu database chama-se sandbox

connectionSql.query(“CREATE DATABASE IF NOT EXISTS sandbox”, err=> )

Agora podemos iniciar o sequelize:

const Sequelize = require(‘sequelize’)
const conn = new Sequelize(schema, user, password, params)

O sequelize pode criar as tabelas no banco de dados baseando-se nas definições dos Models, utilizando para o método sync.

conn.sync()

ok, Tudo pronto

Agora em outro arquivo .js basta chamar o arquivo dos models, onde exportamos os objetos

const db = require(“./modules”)

Agora vamos à parte interessante!

JOINS

A palavra reservada JOIN é utilizada para listar registros de duas ou mais tabelas de um banco de dados. Para utilizar o JOIN as tabelas precisam ser relacionadas. Em Bancos de Dados as tabelas são relacionadas umas com as outras através de chaves primárias e estrangeiras.
Como você pode ver nas tabelas que criamos, a tabela movies possui uma chave primária chamada id, mas também possui as chaves estrangeiras categoriId e ParentalRatingID. Vamos trabalhar com isso daqui pra frente.

Veja nas ilustrações de Steve Stedman que as tabelas devem ser visualizadas na forma de conjuntos, onde uma está à esquerda e outra à direita da operação. Então quando falarmos em Direita/Right, falamos da tabela 1, e Esquerda/Left falamos da tabela 2.

SELECT TWO TABLES

Vamos iniciar com o mais básico de todos. Uma seleção separada de duas tabelas.

http://stevestedman.com/wp-content/uploads/VennDiagram2.pdf

No sequelize ficará assim:

Ok, não é join! Mas ilustra que podemos juntar tudo na mão utilizando as variáveis parent e categs

Vamos ver a resposta no log do sequelize:

Executing (default): SELECT `id`, `name` FROM `parental` AS `parental`;
Executing (default): SELECT `id`, `name` FROM `category` AS `category` LIMIT 10;

Aqui o log mostra exatamente o que o sequelize repassou ao mySql. Ele converteu o comando findAll em SELECT.

E ai está a saída:

INNER JOIN

O Inner Join consiste em unir/juntar informações de duas tabelas considerando apenas os dados que existam ao mesmo tempo nas duas através de suas chaves estrangeira e primária.

http://stevestedman.com/wp-content/uploads/VennDiagram2.pdf

No sequelize ficará assim:

Finalmente temos um primeiro JOIN! Que só foi possível graças ao parâmetro include, onde a segunda tabela deve ser informada, e ao parâmetro required: true, que explico melhor abaixo.

O objeto do do inner join e listar apenas as coincidências entre as tabelas, então o parâmetro required: true elimina da lista os registros que não foram encontrados em na tabela parental. (null não existe lá!)

O parâmetro atributes informa quais colunas queremos, unir no resultado.

attributes: [‘name’],

Na saída teremos:

Veja que na tabela movies, o campo parentalRatinfID não está preenchido para o filme 4 e 5. E é isso que o required: true elimina.

Mas como o sequelize sabe que era pra comparar utilizando o campo parentalRatingID ??

Calma… Lembra da associação belongsTo que fizemos entre os campos?

Vamos ver como ficou o log do sequelize, que prova que a operação foi mesmo um INNER JOIN:

Executing (default): SELECT `movies`.`id`, `movies`.`name`, `movies`.`categoryId`, `movies`.`dubLeg`, `parental`.`name` AS `parental.name` FROM `movies` AS `movies` INNER JOIN `parental` AS `parental` ON `movies`.`parentalRatingID` = `parental`.`id` ORDER BY `movies`.`id` ASC;

LEFT OUTER JOIN

Enquanto o INNER JOIN se preocupa em demonstrar apenas as linhas coincidentes entre as tabelas movies e parental, o LEFT OUTEER JOIN que trazer todas. A tabela da esquerda/LEFT precisa ser listada integralmente, e a da direita aparecerá null se não existir.

http://stevestedman.com/wp-content/uploads/VennDiagram2.pdf

No sequelize ficará assim:

Observe que a única diferença no comando em relação ao INNER JOIN é o parâmetro required que agora é false.

Na saída teremos:

Veja que a coluna parental.name, que foi unida à lista pelo JOIN, possui registros nulos. Isso era o esperado para este tipo de JOIN.

Vamos ver como ficou o log do sequelize, que prova que a operação foi mesmo um LEFT OUTER JOIN:

Executing (default): SELECT `movies`.`id`, `movies`.`name`, `movies`.`categoryId`, `movies`.`dubLeg`, `parental`.`name` AS `parental.name` FROM `movies` AS `movies` LEFT OUTER JOIN `parental` AS `parental` ON `movies`.`parentalRatingID` = `parental`.`id` ORDER BY `movies`.`id` ASC;

RIGHT OUTER JOIN

Vamos pensar na tabela da direita. Queremos aqui uma lista com todos os registros da tabela da direita, mesmo que não exista relação com a tabela da esquerda. Aqui a prioridade é a da direita.

http://stevestedman.com/wp-content/uploads/VennDiagram2.pdf

No sequelize ficará assim:

A primeira coisa que vemos é que não é requerido que exista a informação preenchida na chave estrangeira com o campo da tabela da direita, então o parâmetro required: false.
E para definir qual tabela é de listagem obrigatória, marcamos o parâmetro right: true

Na saída teremos:

Note que nenhum filme foi classificado com classificação parental 12, 14, 21 e 21 na tabela movies, e por isso temos todos os campos da tabela da esquerda com null, estando presente a tabela da direita em parental.name.

Vamos ver como ficou o log do sequelize, que prova que a operação foi mesmo um RIGHT OUTER JOIN:

Executing (default): SELECT `movies`.`id`, `movies`.`name`, `movies`.`categoryId`, `movies`.`dubLeg`, `parental`.`name` AS `parental.name` FROM `movies` AS `movies` RIGHT OUTER JOIN `parental` AS `parental` ON `movies`.`parentalRatingID` = `parental`.`id` ORDER
BY `movies`.`id` ASC;

Na segunda parte deste artigo, falarei mais sobre os outros sabores do JOIN!

PARTE 2

Será que o sequelize (ou mySQL) atende a todos ?!

--

--