Sequelize multiple joins for fetching data - mysql

I am working on a CRUD application with Sequelize and ExpressJS that has the following tables:
Parents
Students
ParentStudents
id
id
id
name
name
idParent
idStudent
I want to query the Parents table and have students key created by a left join between students and ParentStudents on idParent;
I want to get data in the following way:
{
"data":[
{
"name":"nameParent2",
"students":[
{
"name":"Student1"
},
{
"name":"Student2"
}
]
},
{
"name":"nameParent2",
"students":[
{
"name":"Student1"
},
{
"name":"Student2"
}
]
}
]
}
I tried something like this, but is wrong and giving data from ParentStudents:
models.parents.findAll({
include: [{
model: models.parentStudents
}]
}).then(data => {
res.json({
message: "Hello from server!!!",
data: data
});
});
Like this is failing to make the association between Students and ParentStudents. (students is not associated to parentStudents!)
models.parents.findAll({
include: [{
model: models.parentStudents,
include: [{
model: models.students
}]
}]
}).then(data => {
res.json({
message: "Hello from server!!!",
data: data
});
});
My relationships are done like this:
db.parents.hasMany(db.parentStudents);
db.students.hasMany(db.parentStudents);
I also tried Many-to-Many, but still doesn't work:
db.students.belongsToMany(db.parents, { through: db.parentStudents });
db.parents.belongsToMany(db.students, { through: db.parentStudents });
Does someone know how this can be done?

One workaround is:
let parents = await models.parents.findAll();
for (let parent of parents) {
let idParent = parent.dataValues.id;
let students = await models.sequelize.query(`
SELECT * FROM students s
LEFT JOIN parentStudents ps
ON s.id = ps.studentId
WHERE ps.parentId =${idParent};
`);
parent.dataValues['students'] = students;
}

Related

Sequelize raw query to return data as array

my current output is:
{
"students": [
{
"email": "studenthon#gmail.com"
},
{
"email": "studentjon#gmail.com"
}
]
}
What i am trying to achieve is:
{
"students" :
[
"studenthon#gmail.com",
"studentjon#gmail.com"
]
}
How do i get rid of the "email" and put them all into a single array?
My sequelize raw query is as follows:
const studentsFound = await db.sequelize.query(
"SELECT email FROM TeacherStudents INNER JOIN Students on TeacherStudents.studentId = Students.id WHERE teacherId IN (:teachersIds) GROUP BY studentId HAVING COUNT(DISTINCT teacherId) = :arrayLength ",
{
replacements: {
teachersIds: ArrayOfIds,
arrayLength: ArrayOfIds.length
},
type: db.sequelize.QueryTypes.SELECT
}
);
My current solution is
let commonStudentEmail = studentsFound.map(student => {
return student.email;
});
But it seems a little redundant as i feel there should be some sort of configuration.
Sequelize have to distinct one field from another. That's why all fields in a query are named so you know what values in what fields you get exactly.

How to use having clause on relationship with sequelize node

I have 2 models, business and documents in a 1:n relationship, i want to filter the business in two ways,
business that has documents where every document.due_balance is greater than 0
business that has documents where every document.due_balance is equals to 0
I want to make something like this
select
A.name, B.due_balance, sum(B.due_balance) as total_due_balance
from
business A
inner join documents B ON A.id = B.business_id
group by A.id
having total_due_balance > 0;
select
A.name, B.due_balance, sum(B.due_balance) as total_due_balance
from
business A
inner join documents B ON A.id = B.business_id
group by A.id
having total_due_balance = 0;
these will get me what i want, the problem, is that the previus code was made with sequelize ORM, and i can't change it, something like this
const businesses = await db.business.paginate({
attributes: [
...
],
where: {
... //bunch of where
},
page: parseInt(params.page, 10) || 1,
paginate: parseInt(params.limit, 10) || 10,
});
here is where the problem begins, i don't know how to join the tables and use the having to filter it, i have tried addind this
let toInclude;
if (params.contactability === 'with_balance') {
toInclude = {
include : [
{
attributes: [
[db.Sequelize.fn('sum', db.Sequelize.col('due_balance')), 'total_due_balance'],
],
model: db.document,
as: 'documents',
having: db.Sequelize.where(db.Sequelize.fn('sum', db.Sequelize.col('due_balance')), {
$gt: 0,
}),
},
],
};
} else if(params.contactability === 'without_balance') {
toInclude = {
include : [
{
attributes: [
[db.Sequelize.fn('sum', db.Sequelize.col('due_balance')), 'total_due_balance'],
],
model: db.document,
as: 'documents',
having: db.Sequelize.where(db.Sequelize.fn('sum', db.Sequelize.col('due_balance')), {
$eq: 0,
}),
},
],
};
} else {
toInclude = {};
}
const businesses = await db.business.paginate({
attributes: [
...
],
where: {
...
},
...toInclude,
page: parseInt(params.page, 10) || 1,
paginate: parseInt(params.limit, 10) || 10,
});
but that does not work at all, how can i solve this problem?
I don't think HAVING will work without GROUP.
I would move the having clause outside the include section and use the AS aliases.
So, roughly:
group: ['id'], // and whatever else you need
having : { 'documents.total_balance_due' : {$eq : 0 }}
(Making some guesses vis the aliases)
To filter the date from joined table which uses groupby as well, you can make use of HAVING Property, which is accepted by Sequelize.
So with respect to your question, I am providing the answer.
You can make use of this code:
const Sequelize = require('sequelize');
let searchQuery = {
attributes: {
// include everything from business table and total_due_balance as well
include: [[Sequelize.fn('SUM', Sequelize.col('documents.due_balance')), 'total_due_balance']]
},
include: [
{
model: Documents, // table, which you require from your defined model
as: 'documents', // alias through which it is defined to join in hasMany or belongsTo Associations
required: true, // make inner join
attributes: [] // select nothing from Documents table, if you want to select you can pass column name as a string to array
}
],
group: ['business.id'], // Business is a table
having: ''
};
if (params.contactability === 'with_balance') {
searchQuery.having = Sequelize.literal(`total_due_balance > 0`);
} else if (params.contactability === 'without_balance') {
searchQuery.having = Sequelize.literal(`total_due_balance = 0`);
}
Business // table, which you require from your defined model
.findAll(searchQuery)
.then(result => {
console.log(result);
})
.catch(err => {
console.log(err);
});
Note : Change model name or attributes according to your requirement.
Hope this will help you or somebody else!

SequelizeEagerLoadingError while include: [{model: , as: ' ' }]

I want to run this function. I want that the included model: SurveyResult getting an alias.
But i get this error: SequelizeEagerLoadingError: SurveyResult is associated to User using an alias. You've included an alias (Geburtsdatum), but it does not match the alias defined in your association.
const mediImport = await User.findAll({
where: { Id: 1 },
// Select forename as Vorname, name as Nachname
attributes: [['forename', 'Vorname'], ['name', 'Nachname']],
include: [{
model: SurveyResult,
as: 'Geburtsdatum'
}]
})
I know that it is a Problem with my associates, but i cant find the problem
Here are my models.
Model: User
User.associate = function (models) {
User.hasOne(models.Admin)
User.hasOne(models.UserStatus)
User.hasOne(models.SurveyResult, {
})
Model SurveyResult
SurveyResult.associate = function (models) {
SurveyResult.hasOne(models.Survey)
User.hasOne(models.SurveyResult, {})
You need to define the alias on the association level also , like this :
User.hasOne(models.SurveyResult,{ as : 'Geburtsdatum' });

Include ressource link in Sequelize result

I'm building a rest api that uses Sequelize to interact with the database. A query looks like this:
function read_category(req, res) {
Category.findById(req.params.categoryId, {rejectOnEmpty: true}).then(category => {
res.json(category);
}).catch(Sequelize.EmptyResultError, function () {
res.status(404).json({message: 'No category found'});
}
).catch(function (err) {
res.send(err);
}
);
}
Now I want the category object that is returned from Sequelize and then returned to the user to include the linkto the ressource. I could do:
category.dataValues.link = config.base_url + 'categories/' + category.dataValues.id;
Which would result in:
{
"id": 1,
"name": "TestCategory 1",
"position": 1,
"createdAt": "2018-08-19T11:42:09.000Z",
"updatedAt": "2018-08-19T11:42:09.000Z",
"link": "http://localhost:3000/categories/1"
}
Since I have more routes than this one I'm wondering if there's a dynamic way to add the link property to every category. I don't want to save it in the database because the base-url might differ.
Thanks!
Better way to do it is , create a getter method :
const Category = sequelize.define( 'category' , {
....
your_fields
....
},
{
getterMethods:{
link() {
return config.base_url + 'categories/' + this.id;
}
}
});
module.exports = Category;
Then
Category.findAll(...).then(categories => {
// Now there is no need to append data manually , it will added each time when you query
console.log(categories); // <-- Check the output
})

Sails / WaterLine ORM / Has Many Through: Insert data into join table

I'm newbie with Sails/WaterLine ORM
I'm following http://sailsjs.org/documentation/concepts/models-and-orm/associations/through-associations
One question.
How way to insert data into a join table ?
For example: User m - m Pet
User model
module.exports = {
attributes: {
name: {
type: 'string'
},
pets:{
collection: 'pet',
via: 'owner',
through: 'petuser'
}
}
Pet model
module.exports = {
attributes: {
name: {
type: 'string'
},
color: {
type: 'string'
},
owners:{
collection: 'user',
via: 'pet',
through: 'petuser'
}
}
PetUser model (join table)
module.exports = {
attributes: {
owner:{
model:'user'
},
pet: {
model: 'pet'
}
}
}
Pet data is available (some record with ID1, ID2, ID3...)
I want to add new one user with some pets
PetUser ( id , id_of_user, id_of_pet)
1, U1, P1
2, U1, P2
{
"name" : "John",
"pets" : [2,3]
}
UserController
module.exports = {
addUserWithPets: function(req, res) {
User.create(req.body).exec(function(err, user) {
if(err){
throw err;
}else {
/*pets.forEach(function(pet, index){
user.pets.add(pet);
})
user.save(function(err) {});*/
user.pets.add(data);
user.save(function(err) {});
}
return res.ok({
data: user
});
})
}
};
Thanks!
I think this hasn't been implemented yet in sails.
Refer to this question: through associations in sails.js on SO.
Here is what waterline docs say:
Many-to-Many through associations behave the same way as many-to-many associations with the exception of the join table being automatically created for you. This allows you to attach additional attributes onto the relationship inside of the join table.
Coming Soon