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!
Related
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;
}
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.
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' });
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
I am using sequalize in a nodeJs application. I have an entity with a one to may relation (user to many permissions).
I have the following sequalise query to get all users with their permissions... but i think i have been in nosql land for too long :/ and i fear my head no longer works in this way...
models.User.findAll( {
raw: true,
include: [ {
model: models.UserPermissions,
required: false
} ],
logging: console.log
} ).then(function( users ) {
deferred.resolve( users );
}, function(err) {
deferred.reject(err);
});
Produces the following mysql query:
SELECT
`User`.`id`,
`User`.`username`,
`User`.`password`,
`User`.`reset_password_token` AS `resetPasswordToken`,
`User`.`reset_password_expires` AS `resetPasswordExpires`,
`User`.`created_at`, `User`.`updated_at`,
`UserPermissions`.`id` AS `UserPermissions.id`,
`UserPermissions`.`user_id` AS `UserPermissions.user_id`,
`UserPermissions`.`permission` AS `UserPermissions.permission`,
`UserPermissions`.`created_at` AS `UserPermissions.created_at`,
`UserPermissions`.`updated_at` AS `UserPermissions.updated_at`
FROM
`user` AS `User`
LEFT OUTER JOIN `user_permissions` AS `UserPermissions`
ON `User`.`id` = `UserPermissions`.`user_id`;
The result of course is not what I am no used with NoSQL. Instead of recieving one object containing the user and also an array of documents within containing the related permissions I get multiple results for the same user but with different permissions.
See the snapshot of the result set below (I have 2 users in the users table, and 3 permissions in the permissions table).
Is it possible with sequalise, or even mysql for that matter, to return one object for a user with a sub array of their pemissions? or will this really require another query and post query match up?
HACKY FIX:
models.User.findAll( {
raw: true,
include: [ {
model: models.UserPermissions,
required: false
} ]
} ).then(function( users ) {
var newUsersObj = {};
for( var i = 0 ; i < users.length ; ++i ){
if( newUsersObj[users[i].id] ){
newUsersObj[users[i].id].permissions.push( users[i]['UserPermissions.permission'] );
} else {
newUsersObj[users[i].id] = users[i];
newUsersObj[users[i].id].permissions = [ users[i]['UserPermissions.permission'] ];
}
}
deferred.resolve( newUsersObj );
}, function(err) {
deferred.reject(err);
});