How to query many-to-many relationship data in Sequelize - mysql

I have got a many-to-many relationship between two models, users and groups.
I have two models which are specifying the belongsToMany with a foreignKey and through attribute.
Users model
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: DataTypes.STRING,
username: DataTypes.STRING,
facebookId: DataTypes.STRING,
password: DataTypes.STRING,
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Payment);
User.hasMany(models.Friend, {foreignKey: 'userIdLink1'});
User.belongsToMany(models.Group, { through: 'UsersGroups', foreignKey: 'facebookId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return User;
};
groups model
'use strict';
module.exports = function(sequelize, DataTypes) {
var Group = sequelize.define('Group', {
name: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Group.belongsToMany(models.User, {through: 'UsersGroups', foreignKey: 'groupId'});
Group.hasMany(models.Payment)
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return Group;
};
which are being joined via a junction table UsersGroups
This works fine and I can create a new group and it links the user with it successfully but when I try fetch the data the SQL query is trying to find Groups based on User.id as opposed to User.facebookId like I specified in my model User.belongsToMany(models.Group, { through: 'UsersGroups', foreignKey: 'facebookId' });
I call the following code to fetch the data:
const options = {
where: {
facebookId: facebookId,
},
defaults: {
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
facebookId: facebookId
},
include: [
{ model: db.Group }
]
}
db.User.findOrCreate(options)
.then((user) => {
res.send(user)
}, (err) => {
res.status(500).send(err)
})
and it returns a user but with an empty Groups array which is incorrect as there is definitely data there as I can create it fine and see it in the DB.
You can see the SQL query that is generated by Sequelize here:
SELECT `User`.*, `Groups`.`id` AS `Groups.id`, `Groups`.`name` AS `Groups.name`, `Groups`.`createdAt` AS `Groups.createdAt`, `Groups`.`updatedAt` AS `Groups.updatedAt`, `Groups.UsersGroups`.`createdAt` AS `Groups.UsersGroups.createdAt`, `Groups.UsersGroups`.`updatedAt` AS `Groups.UsersGroups.updatedAt`, `Groups.UsersGroups`.`facebookId` AS `Groups.UsersGroups.facebookId`, `Groups.UsersGroups`.`groupId` AS `Groups.UsersGroups.groupId`
FROM (
SELECT `User`.`id`, `User`.`firstName`, `User`.`lastName`, `User`.`email`, `User`.`username`, `User`.`facebookId`, `User`.`password`, `User`.`createdAt`, `User`.`updatedAt` FROM `Users` AS `User` WHERE `User`.`facebookId` = '1341052992643877' LIMIT 1) AS `User`
LEFT OUTER JOIN (`UsersGroups` AS `Groups.UsersGroups`
INNER JOIN `Groups` AS `Groups` ON `Groups`.`id` = `Groups.UsersGroups`.`groupId`
)
ON `User`.`id` = `Groups.UsersGroups`.`facebookId`;
Note the last line
ON `User`.`id` = `Groups.UsersGroups`.`facebookId`
needs to be
ON `User`.`facebookId` = `Groups.UsersGroups`.`facebookId`

Turns out I had to specify facebookId as a primary key in my user model
facebookId: {
type: DataTypes.STRING,
primaryKey: true,
},

Related

Select parent model with child model attribute and destroy it - sequelize/MySQL

I have User and VerifyToken model with token and user_id attributes.
I need to select an user, but with token (from tokens table) value.
I have tried:
await models.User.destroy({
hierarchy: true,
where: {
token: token
}
});
This is User model:
const VerifyToken = require('./verifyToken');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
password: DataTypes.STRING,
email: DataTypes.STRING,
}, {
tableName: 'users',
//syncOnAssociation: true
hierarchy: true
});
User.associate = function (models) {
const {VerifyToken} = models;
User.hasOne(VerifyToken, {
onDelete: 'CASCADE'
});
};
return User;
};
And VerifyToken model:
const User = require('./user');
module.exports = (sequelize, DataTypes) => {
const VerifyToken = sequelize.define('VerifyToken', {
user_id: DataTypes.INTEGER,
token: DataTypes.STRING,
}, {
tableName: 'verify_tokens',
syncOnAssociation: true,
hierarchy: true
});
VerifyToken.associations = function (models) {
const {User} = models;
VerifyToken.belongsTo(User);
};
return VerifyToken;
};
The problem is that, I even don't know where to start. I have tried with include:[{model: models.VerifyToken, where: {}}], but how to use user_id called from the child model ?
What I want is to select an user (parent model) with a value (token in child model) and delete it with one query.
The problem statement you want is to support join and delete in one sequelize operation.
What I want is to select an user (parent model) with a value (token in child model) and delete it with one query.
In sequelize documenation, Model.destroy has no include in the options property.
So the only left option is to select the user_id's from VerifyToken model, then call destroy on User model, where id in user_id got from VerifyToken.
In code it will look like following
const verifyTokens = await VerifyToken.findAll({
where: {
token: {
[Sequelize.Op.In] : YOUR_TOKENS_FOR_WHICH_YOU_WANT_TO_DELETE_YOUR_USER
}
}
}
const userIdsToDestroy = verifyTokens.map(verifyToken => verifyToken.user_id)
await User.destroy({
where: {
id: {
[Sequelize.Op.in] : userIdsToDestroy
}
}
}

Join is not working for sequelize associations belongsToMany

I am testing all the associations with sequelize and I am getting a problem to get joins for many to many with belongsToMany.
https://github.com/lhferrh/Sequelize-Playground.git
When I do the findAll through I get the all null for the join result
I have been checking several combinations but the thing is that the SQL produced by sequelize is working on MySQL directly and it makes me really confused.
This is the sql provided:
SELECT `user`.`userId`, `user`.`name`, `user`.`createdAt`, `user`.`updatedAt`, `cars`.`carId` AS `cars.carId`, `cars`.`make` AS `cars.make`, `cars`.`createdAt` AS `cars.createdAt`, `cars`.`updatedAt` AS `cars.updatedAt`, `cars->favorites`.`favoritesId` AS `cars.favorites.favoritesId`, `cars->favorites`.`date` AS `cars.favorites.date`, `cars->favorites`.`createdAt` AS `cars.favorites.createdAt`, `cars->favorites`.`updatedAt` AS `cars.favorites.updatedAt`, `cars->favorites`.`userId` AS `cars.favorites.userId`, `cars->favorites`.`carId` AS `cars.favorites.carId` FROM `users` AS `user` LEFT OUTER JOIN ( `favorites` AS `cars->favorites` INNER JOIN `cars` AS `cars` ON `cars`.`carId` = `cars->favorites`.`carId`) ON `user`.`userId` = `cars->favorites`.`userId`;
This is the code I am running:
const Users = sequelize.define('user', {
userId: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
},
}
);
const Favorites = sequelize.define('favorites', {
favoritesId: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
date: {
type: Sequelize.DATE,
}
}
);
const Cars = sequelize.define('cars', {
carId: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
make: Sequelize.STRING
})
Users.belongsToMany(Cars, {
through: 'favorites',
sourceKey: 'userId',
foreignKey: 'userId'
});
Cars.belongsToMany(Users, {
through: 'favorites',
sourceKey: 'carId',
foreignKey: 'carId'
});
const intiDataBase = async () => {
await sequelize.sync({force: true});
}
const run = async () => {
const max = 3;
await intiDataBase();
await Promise.all( DATA.users.map( async elem=>
Users.create({...elem})
))
await Promise.all( DATA.cars.map( async elem =>
Cars.create({...elem})
))
for( let i = 0 ; i < 5 ; i++ ){
Favorites.create({
userId: getRandomInt(1 , max),
carId: getRandomInt(0, max)
})
}
const car = await Users.findAll({
include: [{
model: Cars,
through: {
attributes: ['userId', 'carId'],
}
//attributes: ['make'],
}],
raw: true
});
const favorites = await Favorites.findAll({
where:{
userId:2
},
raw: true
});
console.log(car);
console.log(favorites);
}
run();
This is result I get:
name: 'Johan',
createdAt: 2019-10-05T08:57:57.000Z,
updatedAt: 2019-10-05T08:57:57.000Z,
'cars.carId': null,
'cars.make': null,
'cars.createdAt': null,
'cars.updatedAt': null,
'cars.favorites.favoritesId': null,
'cars.favorites.date': null,
'cars.favorites.createdAt': null,
'cars.favorites.updatedAt': null,
'cars.favorites.userId': null,
'cars.favorites.carId': null } ], ...
Probably this is a naming problem but the fact the SQL is working directly makes is really confusing.
I hope any of you can see the error.
By the way, I was also wondering what would be the disadvantage of creating the many to many relationship manually by creating two 1:m associations to the intermediate table?
I found my error. There is an await missing when I am inserting favorites to the database. For that reason the join was giving the the wrong result in the query and the correct one in the database directly.
for( let i = 0 ; i < 5 ; i++ ){
await Favorites.create({
userId: getRandomInt(1 , max),
carId: getRandomInt(0, max)
})
}
As I was expecting, a silly error.
Anyways I am still hesitant about using belongToMany or creating the many to many relationship by myself and use always two steps.

Creating row in both source and association with Sequelize many to many model

I have a many to many relationship between a user and a group. A user has many groups and a group has many users.
I have a users table, a groups table and junction table called usersGroups.
My User model:
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: DataTypes.STRING,
username: DataTypes.STRING,
facebookId: DataTypes.STRING,
password: DataTypes.STRING,
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Payment);
User.hasMany(models.Friend, {foreignKey: 'userIdLink1', allowNull: false});
User.belongsToMany(models.Group, { as: 'Groups', through: 'usersGroups', foreignKey: 'userId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return User;
};
My group model
'use strict';
module.exports = function(sequelize, DataTypes) {
var Group = sequelize.define('Group', {
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Group.belongsToMany(models.User, { as: 'Users', through: 'usersGroups', foreignKey: 'groupId' });
}
},
instanceMethods: {
toJSON: function () {
var values = Object.assign({}, this.get());
delete values.password;
return values;
}
}
});
return Group;
};
When I try create a new group with associated user with the following 2 methods, it creates a new group but no association
const values = {
userId: 1
}
const options = {
include: db.Users
}
db.Group
.create(values, options)
.then( (group) => {
res.send(group)
}).catch(err => {
res.status(500).send({err: err})
})
or
db.Group
.create()
.then( (group) => {
group.addUser({ userId: 1 }).then(result => {
res.send(result)
}).catch(err => {
res.status(500).send({err: err})
})
}).catch(err => {
res.status(500).send({err: err})
})
If you simply want to assign user to newly created group, you need to use addUser, just like you did, but this method accepts first parameter as instance of Model or ID of instance, just like the documentation says
An instance or primary key of instance to associate with this.
So you would have to perform group.addUser(userId).then(...) which in your case would be group.addUser(1).then(...).

Sequelize cli creating association belongsto user or staff

i found many things about associations but nothing to my particular case,
i created some models and i'm trying to associate them,
so i think it is a matter of understanding database modeling too.
I've got the models user and staff, both share an attribute user_id.
user.js
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
user_id: DataTypes.STRING,
fullname: DataTypes.STRING,
username: DataTypes.STRING,
comment: DataTypes.TEXT
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
User.hasMany(models.Sshkey, {foreignKey: 'sshkey_id'})
}
}
});
return User;
};
staff.js
'use strict';
module.exports = function(sequelize, DataTypes) {
var Staff = sequelize.define('Staff', {
user_id: DataTypes.STRING,
fullname: DataTypes.STRING,
username: DataTypes.STRING,
password: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Staff.hasMany(models.Sshkey, {foreignKey: 'sshkey_id'})
}
}
});
return Staff;
};
And i've got a model sshkey which can belong to either an user or a staff member.
I'm using Sequelize cli and haven't done any migrations yet.
And i'm pretty new to Js and creating databases, thinking about the database models and the associations, and i'm curios if i could write or do such thing as:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Sshkey = sequelize.define('Sshkey', {
sshkey_id: DataTypes.STRING,
sshkey: DataTypes.TEXT
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
// My Problem starts here |
// Should i write |
// |
// V
Sshkey.hasOne(models.User || models.Staff, {foreignKey: 'user_id'})
// Or maybe:
// Sshkey.hasOne(models.User, {foreignKey: 'user_id'}) ||
// Sshkey.hasOne(models.Staff, {foreignKey: 'user_id'})
// Should i rather rename models.Staffs foreignKey user_id to staff_id?
// Or maybe:
// Sshkey.hasOne(models.User, {as: 'userkey', foreignKey: 'user_id'})
// Sshkey.hasOne(models.Staff, {as: 'staffkey', foreignKey: 'user_id'})
}
}
});
return Sshkey;
};
What would be a proper solution for the problem that if i later on want to reference a sshkey to either a user or a staff member?
Making two models with staffkeys and userkeys?
Thanks in advance,
BigZ
If you want a 1:m relationship where the foreign key user_id is added to the Sshkey model, should be:
User.hasMany(models.Sshkey, {foreignKey: 'user_id'});
Staff.hasMany(models.Sshkey, {foreignKey: 'user_id'});
Sshkey.belongsTo(models.User, {foreignKey: 'user_id'});
Sshkey.belongsTo(models.Staff, {foreignKey: 'user_id'});
One issue I have with your example is that user_id and sshkey_id are both strings with no constraints around them, which makes them very bad foreignKey's and terrible for designing databases. To get a User and their Sshkey:
User.findAll({
where: {},
include: [ { model: Sshkey } ]
});

Sequelize - Table Pluralization Error, No Instance within Table

I have run into a weird error where I receive a message for a pluralization version of my database table and it is being grouped into my sequel query. I scanned my models and routes and couldn't find any instance of a pluralization of my table name, and when I check the database tables the structures do not mention any pluralization. When I log the query, it looks like the pluralization error comes in at this section of the query
FROM images AS images LEFT OUTER JOIN descriptions AS description ON
Here is the full error message:
Unhandled rejection SequelizeDatabaseError: ER_NO_SUCH_TABLE: Table 'assistant.descriptions' doesn't exist
Here is my Images model:
module.exports = function(sequelize, DataTypes){
var Images = sequelize.define('images', {
pattern: DataTypes.STRING,
color: DataTypes.STRING,
imageUrl: DataTypes.STRING,
imageSource: DataTypes.STRING,
description_id: DataTypes.INTEGER
}, {
classMethods: {
associate: function(db) {
Images.belongsTo(db.Description, {foreignKey: 'description_id'});
}
}
});
return Images;
}
Here is my Description model:
module.exports = function(sequelize, DataTypes) {
var Description = sequelize.define('description', {
description_id: {
type: DataTypes.INTEGER,
primaryKey: true
},
color: DataTypes.STRING,
body: DataTypes.STRING
});
return Description;
}
Here is how I associate the models in dbIndex:
var Sequelize = require('sequelize');
var sequelize = new Sequelize("assistant", "admin", "pwd", {
host: "host",
port: 3306,
dialect: 'mysql'
});
var db = {};
db.Description = sequelize.import(__dirname + "/descriptionModel");
db.Images = sequelize.import(__dirname + "/imagesModel");
db.Images.associate(db);
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
Here is my query:
router.get('/:pattern/:color/result', function(req, res, image){
console.log(req.params.color);
console.log(req.params.pattern);
db.Images.findAll({
where: {
pattern: req.params.pattern,
color: req.params.color
},
include: [db.Description],
attributes: ['id', 'pattern', 'color', 'imageUrl', 'imageSource', 'description_id']
}).then(function(image){
//console.log(doc.descriptions_id);
res.render('pages/suit-result.hbs', {
pattern : req.params.pattern,
color : req.params.color,
image : image
})
});
});
Here is the query before the error:
Executing (default): SELECT `images`.`id`, `images`.`pattern`, `images`.`color`, `images`.`imageUrl`, `images`.`imageSource`, `images`.`description_id`, `description`.`description_id` AS `description.description_id`, `description`.`color` AS `description.color`, `description`.`body` AS `description.body`, `description`.`createdAt` AS `description.createdAt`, `description`.`updatedAt` AS `description.updatedAt` FROM `images` AS `images` LEFT OUTER JOIN `descriptions` AS `description` ON `images`.`description_id` = `description`.`description_id` WHERE `images`.`pattern` = 'solid' AND `images`.`color` = 'navy-blue';
Seqeulize will pluralize your table names by default. To turn it off, include the option freezeTableName: true to all your model definition options that you don't want pluralized. For example, with the Description model:
var Description = sequelize.define('description', {
description_id: {
type: DataTypes.INTEGER,
primaryKey: true
},
color: DataTypes.STRING,
body: DataTypes.STRING
}, {
freezeTableName: true
});