Multiple Sequelize Associations - node.js

I have a few associations between models like so:
Patient.hasMany(Transaction, {
as: 'transactions',
foreignKey: 'patient_id',
sourceKey: 'id'
});
Transaction.belongsTo(Patient, {
as: 'patient',
foreignKey: 'patient_id',
targetKey: 'id'
});
Transaction.hasMany(Transaction, {
as: 'transactions',
foreignKey: 'item_to_pay',
sourceKey: 'id'
});
I'm writing a query to get a list of transactions that belong to a patient and include the models associated with them:
Transaction.findAll({
where: {
patient_id: 1
},
include: [{
model: Patient,
as: 'patient',
attributes: ['first_name']
}, {
model: Transaction,
as: 'transactions',
include: [{
model: Patient,
as: 'patient',
attributes: ['first_name']
}]
}],
});
However when the result returns, it does not include the second nested patient model as I expect it to.
I've also tried writing a different query to see if I can get the desired result:
Transaction.findAll({
where: {
patient_id: 1
},
include: [{
model: Transaction,
as: 'transactions',
include: [{
model: Patient,
as: 'patient',
attributes: ['first_name']
}]
}],
});
But this query errors out and returns 'Unknown column 'patient.id' in field list'.
Does anyone see something wrong with my query or associations? Is there something I'm not understanding about the associations here?

I ended up changing my original query since I couldn't figure it out at the time. I stumbled into the same issue recently and realized the issue came from how I named my associations. I named the transaction.hasMany association as transactions which ended up confusing MySQL with the query that was generated because one of the tables is named transactions.
TLDR, don't name your associations the same name as any of your table names.

Related

How do I eager load multiple foreign keys including an or query from the same table, in Sequelize?

I'm searching for the captain.entry_date but I'm not able to create the query in a sequelize model.
My problem is that for any ship exists a captain but the ship_captain.captain_id sometimes is null.
For this cases the captain can be found about the route_id.
4 Tables :
ship, attributes:[id,name],
captain, attributes: [id, name, route_id, route_date]
ship_captain, attributes: [id, ship_id, route_id, captain_id]
route, attributes: [id, name]
select ship.name, c.name, c.entry_date
from ship left join ship_captain sc on ship.id = sc.ship_id
left join captain c on c.id = sc.captain_id or c.route_id = sc.route_id
What I've try so far is this but I can't give an OR operator into the last join
Ship.hasMany(ShipCaptain, {foreignKey: "ship_id"});
ShipCaptain.belongsTo(Ship, {foreignKey: "ship_id"});
Captain.hasMany(ShipCaptain, {as: "ship_captain_by_id", foreignKey: "captain_id"});
ShipCaptain.belongsTo(Captain, {as: "ship_captain_by_route", foreignKey: "captain_id"});
Captain.hasMany(ShipCaptain, {as: "ship_captain_by_route", foreignKey: "route_id"});
ShipCaptain.belongsTo(Captain, {as: "ship_captain_by_route", foreignKey: "route_id"});
const options = {
attributes: ["name"],
include: [
{
model: Captain,
as: 'ship_captain_per_id',
required: false,
attributes: ["name","route_date"],
},
{
model: Captain,
as: 'ship_captain_per_route',
required: false,
attributes: ["name","route_date"],
}
],
}
const elements = await Ship.findAll(options);
This is only an example code, may be that you want to rearrange the db attributes
but I tried to give my best to clarify the problem. I can't change the customers database.
If you really want to use only one association to get a captain by captain_id or route_id and not to use two associations and map them yourself then you need to define only one association hasOne (instead of hasMany) and always use the on option to join ShipCaptain with Captain by OR:
Ship.hasMany(ShipCaptain, {foreignKey: "ship_id"});
ShipCaptain.belongsTo(Ship, {foreignKey: "ship_id"});
ShipCaptain.belongsTo(Captain, {as: "captain_by_captain", foreignKey: "captain_id"});
...
const options = {
attributes: ["name"],
include: [
{
model: ShipCaptain,
required: false,
include: [{
model: Captain,
required: false,
as: 'captain_by_captain',
attributes: ["name","route_date"],
on: {
[Op.or]: [{
id: Sequelize.col('ship_captain.captain_id'
}, {
id: Sequelize.col('ship_captain.route_id'
}]
}
}
]
},
],
}
const elements = await Ship.findAll(options);

Get all associated records given one match

I have two models with a one to many relation. Given that below query returns a record of ModelA (lets say it has id 1) with 3 associated ModelB (1, 2 and 3).
If I were to replace [1,2,3] in the query to just [1] it would still return the same ModelA record (with id 1) but only with the one associated ModelB (of id 1). How can I modify this query so it returns all three associated ModelB records?
ModelA.findAll({
include: [{
model: JoinTableModel,
where: {
modelB_ID: {
[Op.in]: [1,2,3]
}
},
include: [ModelB]
}]
})
Model definitions like so.
db.ModelA.hasMany(db.JoinTableModel, { foreignKey: 'modelA_ID' })
db.JoinTableModel.belongsTo(db.ModelA, { foreignKey: 'modelA_ID' })
db.ModelB.hasMany(db.JoinTableModel, { foreignKey: 'modelB_ID' })
db.JoinTableModel.belongsTo(db.ModelB, { foreignKey: 'modelB_ID' })
You can try something like this:
JoinTableModel.findAll({
where: {
modelB_ID: {
[Op.in]: [1]
}
},
include: [{
model: ModelA,
include: [{
model: JoinTableModel,
include: [ModelB]
}]
}]
})
If you add an association like this:
ModelA.belongsToMany(ModelB, { through: JoinTableModel })
then you can simplify the above query to this one:
JoinTableModel.findAll({
where: {
modelB_ID: {
[Op.in]: [1]
}
},
include: [{
model: ModelA,
include: [ModelB]
}]
})
For anyone with a similar issue as me, what I ended up doing that works for my case is modifying my initial query like this.
ModelA.findAll({
where: {
id: sequelize.literal(`
ModelA.id
IN (
SELECT modelA_ID FROM JoinTableModel
WHERE modelB_ID IN (1,2,3)
GROUP BY modelA_ID
HAVING COUNT(*) = 3
)`)
},
include: [{
model: JoinTableModel,
include: [ModelB]
}]
})
This way I could easily replace IN (1,2,3) and COUNT(*) 3 with IN (1) and COUNT(*) 1 and it would still work as intended and it doesn't break any other part of the query.
I'm still curious if anyone could solve this without using sequelize.literal in any way or if there is a more efficient way of doing it.

Sequelize: Joining Many to Many tag table with another table in postgresql

I am trying to setup many to many relationship in Sequelize for my Postgres tables using a tag (lookup) table. I was successful in setting up the relationship.
Now my lookup table, has an additional field which is a foreign key to another table. I want the data for that table to be included in my result set. This is where I am having hard time figuring out the syntax.
My models:
Models.user = sequelize.define('user', { //attributes});
Models.school = sequelize.define('school', { //attributes });
Models.role = sequelize.define('role', { //attributes });
Models.userSchools = sequelize.define('user_school', {
user_school_id: { type: Sequelize.INTEGER, primaryKey: true }
});
Models.user.belongsToMany(Models.school, { through: Models.userSchools, foreignKey: 'user_id', otherKey: 'school_id' });
Models.school.belongsToMany(Models.user, { through: Models.userSchools, foreignKey: 'school_id', otherKey: 'user_id' });
Models.userSchools.belongsTo(Models.role, { foreignKey: 'role_id' });
Working query: This query gives me all the users and their schools.
Models.user.findAll({
where: {
//condition
},
include: [Models.school]
})
Now I want to include the role for each schools. None of the below queries are working.
Models.user.findAll({
where: {
//conditions
},
include: [Models.schools, Models.role]
})
Above query is throwing an error saying role is not associated to user, which is expected.
Models.user.findAll({
where: {
//conditions
},
include: [Models.schools, {model: Models.userSchools, include: Models.role}]
})
This query is also throwing an error:
"userSchools is not associated to user".
Can any one help with syntax to how I can get the role information from the userSchools model?
Edit:
I have add a "through" condition which gets me the role_id but not the related role data.
Models.user.findAll({
where: {
//conditions
},
include: [{
model: Models.school,
as: 'schools',
through: {
attributes: ['role_id']
}
}]
})

Sequelize query on join table

I am trying to querying a join table using sequelize:
Here is the model:
db.client.belongsToMany(db.user, {
through: db.clientUser,
onDelete: 'cascade',
});
db.user.belongsToMany(db.client, {
through: db.clientUser,
});
and this is what I am trying to do:
db.user.findAll({
where: {
group_id: 1,
},
include: [{
model: db.clientUser,
where: {
is_manager: 1,
}
}],
raw: true,
})
However I get the following error: client_user is not associated to user!
Any idea what could be the cause of this issue?
You declared a relationship between client from user through clientUser. Although pedantic, its complaint is technically correct: there is no explicitly declared relationship declared between client and clientUser. Nor should there be: your belongsToMany relationship should take care of that. Your query can be adjusted to work with this relationship.
Note: I don't know what tables group_id and is_manager are found in. They may need to be shuffled around.
db.user.findAll({
where: {
group_id: 1,
},
include: [{
model: db.client,
through: {
where: {
is_manager: 1, // Assuming clientUser.is_manager?
},
}],
raw: true,
})

Get Count of Sequelize BelongsToMany Association

I have two models, project and user that are associated with each other in a few ways.
First, a project is owned by a user with the following association:
Project.belongsTo(models.User, { as: 'Owner', foreignKey: 'userId' });
Secondly, a project and user are associated by way of a user liking a project.
Project.belongsToMany(models.User, { as: 'UserLikes', through: models.ProjectLikes, foreignKey: 'projectId' });
User.belongsToMany(models.Project, { as: 'LikedProjects', through: models.ProjectLikes, foreignKey: 'userId' });
Thirdly, a project and user can also be associated by way of a user being part of a project via a Role.
Project.belongsToMany(models.User, { as: 'Users', through: models.ProjectRoles, foreignKey: 'projectId' });
User.belongsToMany(models.Project, { as: 'Roles', through: models.ProjectRoles, foreignKey: 'userId' });
What I am trying to achieve is to find all projects and along with them return the associated "like" count. However I am really struggling to figure out how to do this.
I have read that I should be able to do something like:
models.Project.findAll({
limit: 5,
attributes: ['Project.*', [models.sequelize.fn('COUNT', models.sequelize.col('UserLikes.id')), 'totalLikes']],
include: [ { model: models.User, as: 'UserLikes' }],
group: [ models.Project.rawAttributes.id, models.sequelize.col('UserLikes.id') ]
})
.then(function (projects) {
return res.json(projects);
});
But I get a missing FROM-clause entry for table "UserLikes error.
I've tried changing the associations on both sides to hasMany using a specific joining-table model and then doing:
models.Project.findAll({
limit: 5,
attributes: ['Project.*', 'ProjectLikes.id', [models.sequelize.fn('COUNT', models.sequelize.col('ProjectLikes.id')), 'totalLikes']],
include: [ { model: models.ProjectLikes }],
group: [ models.Project.rawAttributes.id ]
})
.then(function (projects) {
return res.json(projects);
});
But I get the same error, or errors about Ids being ambiguous or errors about Project.id needing to be in the GROUP BY clause (even though it is?!).
I am tearing my hair out trying to figure out something that should be really straight forward... I am almost considering switching back to MongoDB!
Please help... Thanks!
I have the same problem for belongsToMany, but I managed to solve it for hasMany. You have to put attributes: [] for models.ProjectLikes (inside the include) and for fixing the limit bug you have to put subQuery: false in the findAll options.

Resources