Como fazer consultas de junção usando Sequelize no Node.js

104

Estou usando o sequelize ORM; está tudo ótimo e limpo, mas tive um problema ao utilizá-lo com joinconsultas. Tenho dois modelos: usuários e postagens.

var User = db.seq.define('User',{
    username: { type: db.Sequelize.STRING},
    email: { type: db.Sequelize.STRING},
    password: { type: db.Sequelize.STRING},
    sex : { type: db.Sequelize.INTEGER},
    day_birth: { type: db.Sequelize.INTEGER},
    month_birth: { type: db.Sequelize.INTEGER},
    year_birth: { type: db.Sequelize.INTEGER}

});

User.sync().success(function(){
    console.log("table created")
}).error(function(error){
    console.log(err);
})


var Post = db.seq.define("Post",{
    body: { type: db.Sequelize.TEXT },
    user_id: { type: db.Sequelize.INTEGER},
    likes: { type: db.Sequelize.INTEGER, defaultValue: 0 },

});

Post.sync().success(function(){
    console.log("table created")
}).error(function(error){
    console.log(err);
})

Quero uma consulta que responda com um post com as informações do usuário que fez isso. Na consulta bruta, recebo o seguinte:

db.seq.query('SELECT * FROM posts, users WHERE posts.user_id = users.id ').success(function(rows){
            res.json(rows);
        });

Minha pergunta é como posso alterar o código para usar o estilo ORM em vez da consulta SQL?

Jose Sosa
fonte

Respostas:

129
User.hasMany(Post, {foreignKey: 'user_id'})
Post.belongsTo(User, {foreignKey: 'user_id'})

Post.find({ where: { ...}, include: [User]})

O que vai te dar

SELECT
  `posts`.*,
  `users`.`username` AS `users.username`, `users`.`email` AS `users.email`,
  `users`.`password` AS `users.password`, `users`.`sex` AS `users.sex`,
  `users`.`day_birth` AS `users.day_birth`,
  `users`.`month_birth` AS `users.month_birth`,
  `users`.`year_birth` AS `users.year_birth`, `users`.`id` AS `users.id`,
  `users`.`createdAt` AS `users.createdAt`,
  `users`.`updatedAt` AS `users.updatedAt`
FROM `posts`
  LEFT OUTER JOIN `users` AS `users` ON `users`.`id` = `posts`.`user_id`;

A consulta acima pode parecer um pouco complicada em comparação com o que você postou, mas o que ela faz é basicamente criar um alias para todas as colunas da tabela de usuários para garantir que sejam colocadas no modelo correto quando retornadas e não misturadas com o modelo de postagens

Fora isso, você notará que ele faz um JOIN em vez de selecionar a partir de duas tabelas, mas o resultado deve ser o mesmo

Leitura adicional:

Jan Aagaard Meier
fonte
7
E se eu quiser participar apenas de usuários nascidos em 1984? No SQL eu faria:SELECT * FROM posts JOIN users ON users.id = posts.user_id WHERE users.year_birth = 1984
Iwazaru
1
Todos os links estão mortos, não :-(
Manwal
1
Essa pergunta alguma vez foi postada em uma pergunta respondida?
theptrk
1
Precisamos de ambos ou um é o suficiente: User.hasMany (Post, {ForeignKey: 'user_id'}) Post.belongsTo (User, {ForeignKey: 'user_id'})
antes de
1
@antew Quando você chama, User.hasMany(Post)adiciona métodos / atributos ao objeto Usuário e quando chama Post.belongsTo(User)adiciona métodos à classe Post. Se você está sempre chamando de uma direção (ex. User.getPosts ()), você não precisa adicionar nada ao objeto Post. Mas é bom ter os métodos dos dois lados.
Ryan Shillington
170

Embora a resposta aceita não esteja tecnicamente errada, ela não responde à pergunta original nem à pergunta de acompanhamento nos comentários, que foi o que vim aqui procurar. Mas eu descobri, então aqui vai.

Se você deseja encontrar todas as postagens que têm usuários (e apenas aquelas que têm usuários) onde o SQL seria parecido com este:

SELECT * FROM posts INNER JOIN users ON posts.user_id = users.id

O que é semanticamente a mesma coisa que o SQL original do OP:

SELECT * FROM posts, users WHERE posts.user_id = users.id

então é isso que você quer:

Posts.findAll({
  include: [{
    model: User,
    required: true
   }]
}).then(posts => {
  /* ... */
});

A configuração necessária para true é a chave para produzir uma junção interna. Se você quiser uma junção externa esquerda (onde você obtém todas as postagens, independentemente de haver um usuário vinculado), altere obrigatório para falso ou deixe-o desativado, pois é o padrão:

Posts.findAll({
  include: [{
    model: User,
//  required: false
   }]
}).then(posts => {
  /* ... */
});

Se você deseja encontrar todas as postagens pertencentes a usuários cujo ano de nascimento é em 1984, você deseja:

Posts.findAll({
  include: [{
    model: User,
    where: {year_birth: 1984}
   }]
}).then(posts => {
  /* ... */
});

Observe que obrigatório é verdadeiro por padrão assim que você adiciona uma cláusula where em.

Se você quiser todas as postagens, independentemente de haver um usuário anexado, mas se houver um usuário, apenas os nascidos em 1984, adicione o campo obrigatório novamente em:

Posts.findAll({
  include: [{
    model: User,
    where: {year_birth: 1984}
    required: false,
   }]
}).then(posts => {
  /* ... */
});

Se você quiser todas as postagens em que o nome é "Sunshine" e apenas se pertencer a um usuário que nasceu em 1984, você faria o seguinte:

Posts.findAll({
  where: {name: "Sunshine"},
  include: [{
    model: User,
    where: {year_birth: 1984}
   }]
}).then(posts => {
  /* ... */
});

Se você quiser todas as postagens cujo nome seja "Sunshine" e apenas se pertencer a um usuário que nasceu no mesmo ano que corresponde ao atributo post_year na postagem, você faria o seguinte:

Posts.findAll({
  where: {name: "Sunshine"},
  include: [{
    model: User,
    where: ["year_birth = post_year"]
   }]
}).then(posts => {
  /* ... */
});

Eu sei, não faz sentido que alguém faça um post no ano em que nasceu, mas é apenas um exemplo - vá com ele. :)

Eu descobri isso (principalmente) com este documento:

Ryan Shillington
fonte
9
Muito obrigado, isso foi útil
Neo
6
sim, ótima resposta
duxfox
então o que representa posts.user_id = users.idem sua sequência? obrigado
hanzichi de
5
Esta resposta é tão exaustiva que deve ser vinculada no documento do Sequelize
Giorgio Andretti
2
Ótimo tutorial que tenho procurado. Muito obrigado
Andrew Rayan
6
Model1.belongsTo(Model2, { as: 'alias' })

Model1.findAll({include: [{model: Model2  , as: 'alias'  }]},{raw: true}).success(onSuccess).error(onError);
Raja JLIDI
fonte
2

No meu caso, fiz o seguinte. No UserMaster userId é PK e em UserAccess userId é FK de UserMaster

UserAccess.belongsTo(UserMaster,{foreignKey: 'userId'});
UserMaster.hasMany(UserAccess,{foreignKey : 'userId'});
var userData = await UserMaster.findAll({include: [UserAccess]});
Renish Gotecha
fonte