How to include a specific association in sequelize findAll? - javascript

I've created a database with two tables, Users and Points. A user can have many points and a point stores the ID of the user who sent it and the user who received it. I am trying to query for a table grouped by user showing the sum of the amounts of all their points, which is working querying raw in postgresql but not in sequelize.
Working in postgresql:
Creating the models with sequelize:
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
telegram_id: {
type: DataTypes.INTEGER,
allowNull: false,
unique: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
tableName: "users",
sequelize: sequelize, // this bit is important
}
);
Point.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
amount: {
type: DataTypes.INTEGER,
allowNull: false,
},
to_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
from_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{
tableName: "points",
sequelize: sequelize, // this bit is important
}
);
User.hasMany(Point, {
sourceKey: "telegram_id",
foreignKey: "to_id",
as: "recievedPoints", // this determines the name in `associations`!
});
User.hasMany(Point, {
sourceKey: "telegram_id",
foreignKey: "from_id",
as: "sentPoints", // this determines the name in `associations`!
});
Point.belongsTo(User, {
foreignKey: "to_id",
targetKey: "telegram_id",
as: "toUser",
});
Point.belongsTo(User, {
foreignKey: "from_id",
targetKey: "telegram_id",
as: "fromUser",
});
Attempting to make same query with sequelize:
const points = await Point.findAll({
attributes: [
"users.name",
"points.to_id",
[Sequelize.fn("SUM", Sequelize.col("points.amount")), "points.amount"],
],
include: ["toUser"],
group: ["users.name", "points.to_id"],
});
Resulting error:
SequelizeDatabaseError: invalid reference to FROM-clause entry for table "users"
SQL generated by sequelize:
SELECT "users"."name", "points"."to_id", SUM("points"."amount") AS "points.amount", "toUser"."id" AS "toUser.id", "toUser"."telegram_id" AS "toUser.telegram_id", "toUser"."name" AS "toUser.name", "toUser"."createdAt" AS "toUser.createdAt", "toUser"."updatedAt" AS "toUser.updatedAt" FROM "points" AS "Point"
LEFT OUTER JOIN "users" AS "toUser" ON "Point"."to_id" = "toUser"."telegram_id" GROUP BY "users"."name", "points"."to_id";

RAW QUERY :
SELECT "users"."name", "points"."to_id", SUM("points"."amount") AS "points.amount", "toUser"."id" AS "toUser.id", "toUser"."telegram_id" AS "toUser.telegram_id", "toUser"."name" AS "toUser.name", "toUser"."createdAt" AS "toUser.createdAt", "toUser"."updatedAt" AS "toUser.updatedAt"
FROM "points" AS "Point"
LEFT OUTER JOIN "users" AS "toUser" ON "Point"."to_id" = "toUser"."telegram_id" GROUP BY "users"."name", "points"."to_id";
As per your raw query :
Change "users" to "toUser" every where
Change "points" to "Point" every where , like this :
const points = await Point.findAll({
attributes: [
"toUser.name",
"Point.to_id",
[Sequelize.fn("SUM", Sequelize.col("Point.amount")), "Point.amount"],
],
include: ["toUser"],
group: ["toUser.name", "Point.to_id"],
});

Related

Sequelize - avoid deleting rows of many-to-many association when inserting a new one

My problem is that when I execute this code:
const task = await db.tasks.findByPk(1)
const user = await db.users.findByPk(4)
await user.setTasks(task)
This happens:
Executing (default): SELECT `tasks`.`id`, `tasks`.`start_date` FROM `tasks` AS `tasks` WHERE `tasks`.`id` = '1';
Executing (default): SELECT `id`, `name`, `surname`, `email`, `role` FROM `users` AS `users` WHERE `users`.`id` = '4';
Executing (default): SELECT `id`, `task_id`, `user_id` FROM `users_tasks` AS `users_tasks` WHERE `users_tasks`.`user_id` = 4;
Executing (default): DELETE FROM `users_tasks` WHERE `user_id` = 4 AND `task_id` IN (2)
Executing (default): INSERT INTO `users_tasks` (`id`,`task_id`,`user_id`) VALUES (NULL,1,4);
The user with id 4 had a previous association with a task with id 2. The issue is, I want to keep that association! It is perfectly fine for the purpose of my application for a user to be assigned to more tasks (and vice versa).
I would actually say that that's the normal desired behaviour of a many-to-many relationship... why is Sequelize deleting the row on users_tasks before adding a new one, and how can I prevent this from happening?
Here is my user model:
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('users', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(50),
allowNull: false
},
surname: {
type: DataTypes.STRING(50),
allowNull: false
},
email: {
type: DataTypes.STRING(256),
allowNull: false
},
}, {
tableName: 'users',
timestamps: false,
})
User.associate = function (models) {
User.belongsToMany(models.tasks, {through: 'users_tasks', foreignKey: 'user_id'})
}
return User
}
Here is my task model:
module.exports = function(sequelize, DataTypes) {
var Task = sequelize.define('tasks', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
start_date: {
type: DataTypes.DATE,
allowNull: true,
},
}, {
tableName: 'tasks',
timestamps: false
})
Task.associate = function (models) {
Task.belongsToMany(models.users, {through: 'users_tasks', foreignKey: 'task_id'})
}
return Task
}
And here is my join table:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('users_tasks', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
task_id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
references: {
model: 'tasks',
key: 'id'
}
},
user_id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'users_tasks',
timestamps: false
})
}
Do I need to change something on the models definitions, or do I need to change something in the way I call setTasks? Or else, what do I need to do? Why is Sequelize enforcing this weird and unwanted behaviour of deleting previously existing associations?
I found out that I should be using add instead of set if I want to keep the preexisting relations.
So, the correct way of achieving the result I want is this:
const task = await db.tasks.findByPk(1)
const user = await db.users.findByPk(4)
await user.addTasks(task)

Sequelize belongsToMany: TypeError: Cannot read property 'hasBrand' of undefined

I have User and Brand models.
A user can have many brands.
A brand can belong many users.
I defined another model for many-to-many associations. It named UserBrand.
// user.js
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
hasBrand: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
})
User.associate = models=> {
User.belongsToMany(models.Brand, {
through: models.UserBrand,
foreignKey: 'user',
})
}
// brand.js
Brand.belongsToMany(models.User, {
through: models.UserBrand,
foreignKey: 'brand',
})
// userBrand.js
const UserBrand = sequelize.define('UserBrand', {
status: {
type: DataTypes.INTEGER,
allowNull: false,
},
title: {
type: DataTypes.STRING,
defaultValue: '',
},
})
UserBrand.associate = models=> {
UserBrand.belongsTo(models.User, { foreignKey: 'user', targetKey: 'id' })
UserBrand.belongsTo(models.Brand, { foreignKey: 'brand', targetKey: 'id' })
}
When I run my application, it shows TypeError: Cannot read property 'hasBrand' of undefined. I can not find any relation between hasBrand and association, but remove hasBrand it works
How can I fix it in case I still want to keep hasBrand field?

Sequelize foreign key error on Heroku but not local testing

I am new developer and am trying to teach myself Sequelize and mysql with some little test projects. What I have right now is a little RPG team strength analyzer. I have a SQL table of Units, which has schema (id, name, elementOne, elementTwo) - integer, string, string, string.
For now, the elementOne and ElementTwo tables are both the same 18 string values because I couldn't figure out how to set up the Sequelize query with foreign keys refs to the same table (e.g. just 'elements').
Adding to the Unit table works fine on a local server, but breaks on Heroku ONLY when trying to add a third unit with the following error:
Error was: { SequelizeForeignKeyConstraintError: Cannot add or update a child
row: a foreign key constraint fails (`heroku_f4daeab1e260595`.`units`,
CONSTRAINT `units_ibfk_1` FOREIGN KEY (`id`) REFERENCES `elementtwos` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE)
Here are all the tables and the relationship declarations.
const Unit = sequelize.define('unit', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: false
},
image: {
type: Sequelize.STRING,
allowNull: true,
unique: false
},
elementOne: {
type: Sequelize.INTEGER,
allowNull: true,
references: {
model: Element,
key: 'id'
}
},
elementTwo: {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: 10001,
references: {
model: ElementTwo,
key: 'id'
}
}
});
const Element = sequelize.define('element', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: false
}
});
const ElementTwo = sequelize.define('elementtwo', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: false
}
});
After these are all loaded, I set up the following:
Unit.belongsTo(Element, {foreignKey: 'elementOne'});
Unit.belongsTo(ElementTwo, {foreignKey: 'elementTwo'});
ElementTwo.hasMany(Unit, {foreignKey: 'id'});
Element.hasMany(Unit, {foreignKey: 'id'});
And this is the query that Sequelize is doing (in a Unit.create({...}):
INSERT INTO `units`
(`id`,`name`,`image`,`elementOne`,`elementTwo`,`createdAt`,`updatedAt`) VALUES
(DEFAULT,'raichu','http://longimgurl.png',13,10001,'2017-06-14
12:57:54','2017-06-14 12:57:54');
If anyone can offer any advice it would be greatly appreciated.
this is mysql error and because you define foreign key constraint on you table and try to insert unavailable value in fk field that does not exit in target table,
check element and elementTwo table and make sure this values are available

Sequelize many-to-many association - Method not found

I have two n:m sequelize models as shown below
// Organization Model
module.exports = {
attributes: {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
required: true
},
},
associations: function() {
Organization.belongsToMany(Contact, {
through : OrganizationContact,
foreignKey: {
name: 'organizationId',
allowNull: false
}
});
}
};
// OrganizationContact Model
module.exports = {
attributes: {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
}
}
}
// Contact Model
module.exports = {
attributes: {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstname: {
type: Sequelize.STRING,
required: true
},
lastname: {
type: Sequelize.STRING,
required: false
},
},
associations: function() {
Contact.belongsToMany(Organization, {
through : OrganizationContact,
foreignKey: {
name: 'contactId',
allowNull: false
}
});
}
};
I am trying to insert a contact and attach it to an existing organization. My data looks like
{
"firstname" : "Mathew",
"lastname" : "Brown",
"organizationId" : 1 // Add the contact to an existing organization. I am missing something here.
}
Note : There can be multiple contacts attached to multiple organizations. An organization is created before a contact.
Based on this documentation, after saving the contact when I tried
Organization.addContact(contact);
I get an exception saying
Organization.addContact is not a function
The addContact method should be called on instance of Organization rather than on the model itself, just as you do in the example code.
Organization.create(organizationData).then(organization => {
organization.addContact(contact).then(() => {
// contact was added to previously created organization
});
});
You do not need the organizationId attribute in your contact create data. If you want to add new contact to the organization with id: 1, then you first need to return the organization instance and then perform the addContact method
Organization.findByPrimary(1).then(organization => {
organization.addContact(contact).then(() => {
// contact was added to organization with id = 1
});
});

Query junction table without getting both associations in Sequelize

Consider the following models:
var User = sequelize.define('User', {
_id:{
type: Datatypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: Datatypes.STRING,
email:{
type: Datatypes.STRING,
unique: {
msg: 'Email Taken'
},
validate: {
isEmail: true
}
}
});
var Location= sequelize.define('Location', {
_id:{
type: Datatypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: Datatypes.STRING,
address: type: Datatypes.STRING
});
Location.belongsToMany(User, {through: 'UserLocation'});
User.belongsToMany(Location, {through: 'UserLocation'});
Is there a way to query the UserLocation table for a specific UserId and get the corresponding Locations. Something like:
SELECT * FROM Locations AS l INNER JOIN UserLocation AS ul ON ul.LocationId = l._id WHERE ul.UserId = 8
From what I can find you can do something similar to:
Location.findAll({
include: [{
model: User,
where: {
_id: req.user._id
}
}]
}).then( loc => {
console.log(loc);
});
However, this returns the Locations, UserLocation junctions, and User which it is joining the User table when I do not need any user information and I just need the Locations for that user. What I have done is working, however, the query against the junction table is prefered instead of the lookup on the User table.
I hope this is clear. Thanks in advance.
Edit
I actually ended up implementing this in a different way. However, I am still going to leave this as a question because this should be possible.
declaring junction table as separate class, something like this
var UserLocation = sequelize.define('UserLocation', {
//you can define additional junction props here
});
User.belongsToMany(Location, {through: 'UserLocation', foreignKey: 'user_id'});
Location.belongsToMany(User, {through: 'UserLocation', foreignKey: 'location_id'});
then you can query junction table same as any other model.

Categories