Multiple tables join issue in sequelize in node js - node.js

I have 6 tables
Employees,Salary,Deduction,DeductionTypes,Allowance,AllowancesTypes
Here is the table relations with a diagram
I can't understand how to create a join query
Employee.belongsTo(Salary, { foreignKey: 'employee_id' });
Salary.belongsToMany(Deduction, {foreignKey: 'salary_id' });
Deduction.belongsToMany(DeductionType, {foreignKey: 'deduction_type_id' });
Salary.belongsToMany(Allowance, {foreignKey: 'salary_id' });
Allowance.belongsToMany(AllowanceType, {foreignKey: 'allowance_type_id' });
Here is my find query
const payRunData = Employee.findAll({
include: [{
model: Salary, attributes: ['salary_id', 'basic_salary'],
include: [
{
model: Deduction, attributes: ['amount'],
include: [{
model: DeductionType, attributes: ['epf_enable', 'type', 'pre_tax', 'pay_run_editable'],
}]
},
{
model: Allowance, attributes: ['amount'],
include: [{
model: AllowanceType, attributes: ['epf_enable', 'type', 'payee_enable', 'pay_run_editable'],
}]
}
]
}
],
attributes: ['employee_id', 'given_names'],
});
Here is the sql join query what I'm expecting
select s.salary_id , e.employee_id, e.given_names, d.amount as 'deduction_amount',
dty.`type` as 'deduction_type', dty.epf_enable as 'deductType_epf_enable', dty.pre_tax, dty.pay_run_editable as 'deductType_pay_run_editable',
a.amount as 'allowance_amount', aty.`type`as 'allowanceType_type', aty.epf_enable as 'allowanceType_epf_enable',
aty.payee_enable as 'allowanceType_payee', aty.pay_run_editable as 'allowanceType_pay_run_editable'
from employees as e inner join salary as s on e.employee_id = s.salary_id
inner join deductions as d on s.salary_id = d.salary_id
inner join deduction_types as dty on d.deduction_type_id = dty.deduction_type_id
inner join allowances as a on s.salary_id = a.salary_id
inner join allowance_types as aty on a.allowance_type_id = aty.allowance_type_id
When I execute this,it's returning this error
salary.belongsToMany(deduction) requires through option, pass either a
string or a model"
please help me to solve this

Related

Sequelize get all records if value exists on N:N table

I want to achieve this query with sequelize
SELECT U.username, G.groupName
FROM [user] U INNER JOIN [userGroup] UG ON (U.id = UG.userId) INNER JOIN [group] G ON (G.id = UG.groupId)
WHERE U.profileId = #profile AND EXISTS (SELECT groupId FROM [userGroup] WHERE groupId = #group AND userId = U.id)
the expected result are all the users that match with the profile and all theirs groups in which some group should be present
this is my relevant code of my associations layer
db.users.belongsToMany(db.group, {
as: 'Groups',
through: UserGroup,
foreignKey: 'userId',
otherKey: 'groupId',
});
db.group.belongsToMany(db.users, {
as: 'Users',
through: UserGroup,
foreignKey: 'groupId',
otherKey: 'userId',
});
and the one from the controller
User.findAll({
where: {profileId = Number(profileType)},
include: [{
model: Group,
as: 'Groups',
attributes: ['groupName'],
where: [
{
[Op.and]: sequelize.literal(
`EXISTS (SELECT [UG].groupId FROM [userGroup] AS [UG] WHERE [UG].groupId = ${group})`
),
};
]
required: true,
}]
My principal problem is how to filter by userId in the sequelize subquery like i did in the SQL query example from the begining
You need to move this subquery condition to the User's conditions because you have AND userId = U.id. Something like this:
User.findAll({
where: {
[Op.and]: [{
profileId = Number(profileType)
},
sequelize.literal(
`EXISTS (SELECT [UG].groupId FROM [userGroup] AS [UG] WHERE [UG].groupId = ${group} AND [UG].userId=[User].id)`
)]
},
include: [{
model: Group,
as: 'Groups',
attributes: ['groupName'],
required: true,
}]
You should look at generated SQL query and correct the [User] table alias if needed in AND [UG].userId=[User].id

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);

sequelize get column value of referenced table

I have three tables meetings, rooms and projects
meetings.belongsTo(models.rooms, { foreignKey: "room_id" , targetKey: "id"});
rooms.hasMany(models.meetings, { foreignKey : "id", targetKey : "room_id"});
rooms.belongsTo(models.projects, { foreignKey: "project_id", targetKey: "id"});
projects.hasMany(models.rooms, {foreignKey:"id", targetKey:"project_id"});
id, room_id and project_id are primary key of their respective table.
I want to find value of some column of projects for a particular meeting id. How to write a single query using sequelize nodejs to do this?
below query need to execute using sequelize which is giving correct result
select project_meta from projects p
inner join rooms r on p.id = r.project_id
inner join meetings m on r.id = m.room_id
where m.id = "QBZJ0TK7V6NFSPPWGFNCN";
wrote following
projects.findAll({
include: [{
model: rooms,
include: [{
model: meetings,
where: {
id: "QBZJ0TK7V6NFSPPWGFNCN"
}
}]
}],
attributes: ['project_meta']
}
but it is executing different query and giving unexpected result
is there any problem with association?
You must use SQL joins (sequelize's include)
function myQuery(meetingId) {
let options = {};
options.attributes = ['column1', 'column2', ...]; // The specific arrtibutes you need.
options.include = [
{
model: RoomModel,
include: [
{
model: MeetingModel,
where: {
id: meetingId // The specific meeting id
}
}
]
}
];
ProjectModel.findAll(options);
}

How to set complex join conditions in sequelize?

I'm trying to encode a query to a database using sequelize. This is exactly the sentence I want to transcript:
SELECT matches.uid_prop, matches.uid_player, matches.porcentaje, users.uid, profile_users.name, profile_users.profile, profile_users.birthday, profile_users.location
FROM (matches JOIN users ON (((matches.uid_prop = users.uid) AND (matches.uid_player = 11)) OR ((matches.uid_player = users.uid) AND (matches.uid_prop = 11)))) JOIN profile_users USING(uid)
WHERE (matches.uid_player = 11) OR (matches.uid_prop = 11);
I don't know if I should change the associations in order to build the join condition as I want
Matches.belongsTo(User, {as: 'match_prop', foreignKey: 'uid_prop'});
User.hasMany(Matches, {as: 'user_prop', foreignKey: 'uid_prop'});
Matches.belongsTo(User, {as: 'match_player', foreignKey: 'uid_player'});
User.hasMany(Matches, {as: 'user_player', foreignKey: 'uid_player'});
or add a where and specify the relation at the include between matches and user. If that's the case, how could I make reference to a field of matches from a where statement in the include made with user so I could compare, e.g., User.uid and Matches.uid_prop.
I've tried this notation but it didn't work: $Matches.uid_prop$
Matches.findAll({
attributes: ['porcentaje'],
where: {
$or: [
{uid_prop: uid},
{uid_player: uid}
]
},
include: [{
model: User,
attributes: ['uid'],
include: [{
model: Profile_user,
attributes: ['name', 'profile', 'birthday', 'location']
}]
}]
})
I think all you need is required : true ( INNER JOIN ) like :
Matches.findAll({
attributes: ['porcentaje'],
where: {
$or: [
{uid_prop: uid},
{uid_player: uid}
]
},
include: [{
model: User,
attributes: ['uid'],
required : true , //<-------- HERE -------
include: [{
model: Profile_user,
attributes: ['name', 'profile', 'birthday', 'location']
}]
}]
})
Your query is making the LEFT JOIN , to make it INNER JOIN force fully , you can use required : true.

How to count a group by query in NodeJS Sequelize

In Rails I can perform a simple ORM query for the number of Likes a model has:
#records = Model
.select( 'model.*' )
.select( 'count(likes.*) as likes_count' )
.joins( 'LEFT JOIN likes ON model.id = likes.model_id' )
.group( 'model.id' )
This generates the query:
SELECT models.*, count(likes.*) as likes_count
FROM "models" JOIN likes ON models.id = likes.model_id
GROUP BY models.id
In Node Sequelize, any attempt at doing something similar fails:
return Model.findAll({
group: [ '"Model".id' ],
attributes: ['id', [Sequelize.fn('count', Sequelize.col('"Likes".id')), 'likes_count']],
include: [{ attributes: [], model: Like }],
});
This generates the query:
SELECT
Model.id,
count(Likes.id) AS likes_count,
Likes.id AS Likes.id # Bad!
FROM Models AS Model
LEFT OUTER JOIN Likes
AS Likes
ON Model.id = Likes.model_id
GROUP BY Model.id;
Which generates the error:
column "Likes.id" must appear in the GROUP BY clause or be used in an aggregate function
It's erroneously selecting likes.id, and I have no idea why, nor how to get rid of it.
This sequelize github issue looks totally like your case:
User.findAll({
attributes: ['User.*', 'Post.*', [sequelize.fn('COUNT', 'Post.id'), 'PostCount']],
include: [Post]
});
To resolve this problem we Need to upgrade to latest version of sequelize and include raw = true,
Here is How I had done after lot of iteration and off-course googling.
getUserProjectCount: function (req, res) {
Project.findAll(
{
attributes: ['User.username', [sequelize.fn('COUNT', sequelize.col('Project.id')), 'ProjectCount']],
include: [
{
model: User,
attributes: [],
include: []
}
],
group: ['User.username'],
raw:true
}
).then(function (projects) {
res.send(projects);
});
}
where my reference models are
//user
var User = sequelize.define("User", {
username: Sequelize.STRING,
password: Sequelize.STRING
});
//project
var Project = sequelize.define("Project", {
name: Sequelize.STRING,
UserId:{
type:Sequelize.INTEGER,
references: {
model: User,
key: "id"
}
}
});
Project.belongsTo(User);
User.hasMany(Project);
after migration ORM create 'Users' & 'Projects' table into my postgres server.
Here is SQL Query by ORM
SELECT
"User"."username", COUNT("Project"."id") AS "ProjectCount"
FROM
"Projects" AS "Project"
LEFT OUTER JOIN "Users" AS "User" ON "Project"."UserId" = "User"."id"
GROUP BY
"User"."username";
What worked for me counting column A and grouping by column B
const noListingsPerRetailer = Listing.findAll({
attributes: [
'columnA',
[sequelize.fn('COUNT', sequelize.col('columnB')), 'labelForCountColumn'],
],
group:["columnA"]
});

Resources