I am using sequelize ORM; everything is great and clean, but I had a problem when I use it with join queries.
I have two models: users and posts.
var User = db.seq.define('User',{
username: { type: db.Sequelize.STRING},
email: { type: db.Sequelize.STRING},
password: { type: db.Sequelize.STRING},
sex : { type: db.Sequelize.INTEGER},
day_birth: { type: db.Sequelize.INTEGER},
month_birth: { type: db.Sequelize.INTEGER},
year_birth: { type: db.Sequelize.INTEGER}
});
User.sync().success(function(){
console.log("table created")
}).error(function(error){
console.log(err);
})
var Post = db.seq.define("Post",{
body: { type: db.Sequelize.TEXT },
user_id: { type: db.Sequelize.INTEGER},
likes: { type: db.Sequelize.INTEGER, defaultValue: 0 },
});
Post.sync().success(function(){
console.log("table created")
}).error(function(error){
console.log(err);
})
I want a query that respond with a post with the info of user that made it. In the raw query, I get this:
db.seq.query('SELECT * FROM posts, users WHERE posts.user_id = users.id ').success(function(rows){
res.json(rows);
});
My question is how can I change the code to use the ORM style instead of the SQL query?
While the accepted answer isn't technically wrong, it doesn't answer the original question nor the follow up question in the comments, which was what I came here looking for. But I figured it out, so here goes.
If you want to find all Posts that have Users (and only the ones that have users) where the SQL would look like this:
SELECT * FROM posts INNER JOIN users ON posts.user_id = users.id
Which is semantically the same thing as the OP's original SQL:
SELECT * FROM posts, users WHERE posts.user_id = users.id
then this is what you want:
Posts.findAll({
include: [{
model: User,
required: true
}]
}).then(posts => {
/* ... */
});
Setting required to true is the key to producing an inner join. If you want a left outer join (where you get all Posts, regardless of whether there's a user linked) then change required to false, or leave it off since that's the default:
Posts.findAll({
include: [{
model: User,
// required: false
}]
}).then(posts => {
/* ... */
});
If you want to find all Posts belonging to users whose birth year is in 1984, you'd want:
Posts.findAll({
include: [{
model: User,
where: {year_birth: 1984}
}]
}).then(posts => {
/* ... */
});
Note that required is true by default as soon as you add a where clause in.
If you want all Posts, regardless of whether there's a user attached but if there is a user then only the ones born in 1984, then add the required field back in:
Posts.findAll({
include: [{
model: User,
where: {year_birth: 1984}
required: false,
}]
}).then(posts => {
/* ... */
});
If you want all Posts where the name is "Sunshine" and only if it belongs to a user that was born in 1984, you'd do this:
Posts.findAll({
where: {name: "Sunshine"},
include: [{
model: User,
where: {year_birth: 1984}
}]
}).then(posts => {
/* ... */
});
If you want all Posts where the name is "Sunshine" and only if it belongs to a user that was born in the same year that matches the post_year attribute on the post, you'd do this:
Posts.findAll({
where: {name: "Sunshine"},
include: [{
model: User,
where: ["year_birth = post_year"]
}]
}).then(posts => {
/* ... */
});
I know, it doesn't make sense that somebody would make a post the year they were born, but it's just an example - go with it. :)
I figured this out (mostly) from this doc:
http://docs.sequelizejs.com/en/latest/docs/models-usage/#eager-loading
User.hasMany(Post, {foreignKey: 'user_id'})
Post.belongsTo(User, {foreignKey: 'user_id'})
Post.find({ where: { ...}, include: [User]})
Which will give you
SELECT
`posts`.*,
`users`.`username` AS `users.username`, `users`.`email` AS `users.email`,
`users`.`password` AS `users.password`, `users`.`sex` AS `users.sex`,
`users`.`day_birth` AS `users.day_birth`,
`users`.`month_birth` AS `users.month_birth`,
`users`.`year_birth` AS `users.year_birth`, `users`.`id` AS `users.id`,
`users`.`createdAt` AS `users.createdAt`,
`users`.`updatedAt` AS `users.updatedAt`
FROM `posts`
LEFT OUTER JOIN `users` AS `users` ON `users`.`id` = `posts`.`user_id`;
The query above might look a bit complicated compared to what you posted, but what it does is basically just aliasing all columns of the users table to make sure they are placed into the correct model when returned and not mixed up with the posts model
Other than that you'll notice that it does a JOIN instead of selecting from two tables, but the result should be the same
Further reading:
http://docs.sequelizejs.com/en/latest/docs/associations/#one-to-one-associations
http://docs.sequelizejs.com/en/latest/docs/associations/#one-to-many-associations
http://docs.sequelizejs.com/en/latest/docs/models-usage/#eager-loading
Model1.belongsTo(Model2, { as: 'alias' })
Model1.findAll({include: [{model: Model2 , as: 'alias' }]},{raw: true}).success(onSuccess).error(onError);
In my case i did following thing. In the UserMaster userId is PK and in UserAccess userId is FK of UserMaster
UserAccess.belongsTo(UserMaster,{foreignKey: 'userId'});
UserMaster.hasMany(UserAccess,{foreignKey : 'userId'});
var userData = await UserMaster.findAll({include: [UserAccess]});
Create associations in model file and then use include for joins you can use inner includes too join tables.
----model.js
blog1.hasMany(blog2, {foreignKey: 'blog_id'})
blog2.belongsTo(blog1, {foreignKey: 'blog_id'})
-----controller.js
blog2.find({ where: {blog_id:1}, include: [blog1]})
Related
I have 'Ingredient' and 'Log' Tables like this
[Ingredient Table]
id
..
...
[Log Table]
id
Ingredient_id
record_date
..
...
the relationship is Log.belongsTo(Ingredeint)
how can I find all ingredients which have at least 1 row of Log?
I mean when I searching the Ingredients, If there is no related Log on Ingredient, I don't want to include that Ingredient on my search result.
what I did now to accomplish that is
const ingredients = await Ingredient.findAll({
include: {
model: Log
},
group: "id",
attributes: {
include: [
[sequelize.fn("COUNT", sequelize.col("record_date")), "order_count"]
]
}
})
const sortedIngredient = ingredients
.filter(ingredient => ingredient.dataValues.order_count > 0)
But I think there would be a better way.
Thank you for reading this.
If I understand you correctly you want to do a inner join in your include, then you would only return ingredients that have some match in the included model.
Try to change the include to:
include: {
model: Log
required: true, // <-- Add this row
}
More info about require can be found in the docs: https://sequelize.org/master/class/lib/model.js~Model.html#static-method-findAll
Another option that maybe could help you is to add having to filter on a aggregated column, like this:
const ingredients = await Ingredient.findAll({
include: {
model: Log,
},
group: "id",
attributes: {
include: [
[sequelize.fn("COUNT", sequelize.col("record_date")), "order_count"],
],
},
having: sequelize.literal("`order_count` > 0"), // <-- Add this row
});
Does that help?
I have been trying to define a relationship between 3 tables and then create them all in one create function. For some reason, while creating the 3 models, the linking IDs (foreign keys) are undefined and are not passing on. Here are the associations:
Person.js:
models.person.Lead = models.person.hasMany(models.lead, {
onDelete: "CASCADE",
foreignKey: "person_id"
});
Lead.js:
models.lead.Person = models.lead.belongsTo(models.person, {foreignKey: 'person_id'});
models.lead.Sealant_customer = models.lead.hasOne(models.sealant_customer, {
onDelete: "CASCADE",
foreignKey: 'lead_id'
})
sealantCustomer.js:
models.sealant_customer.Lead = models.sealant_customer.belongsTo(models.lead);
The build function:
let sealantCustomer = models.sealant_customer.build({
address: body.address,
city: body.city,
roof_size: body.roofSize,
last_sealed: body.lastSealed,
existingSealant: body.existingSealant,
leaks_freq: body.leaksFrequency,
floor: body.floor,
elevator: body.elevator,
panels: body.panels,
home_type: body.homeType,
urgency: body.urgency,
next_step: body.nextStep,
more_info: body.moreInfo,
lead: {
site,
url: body.url,
date,
ip: body.ip,
person: {
name: body.name,
email: body.email,
phone: body.phone,
date,
city: body.city ? body.city : undefined,
address: body.address ? body.address : undefined,
}
}
}, {
include: [{
model: models.lead,
association: models.sealant_customer.Lead,
include: [{
model: models.person,
association: models.lead.Person
}]
}]
})
The outputted object is good except for the fact that lead_id and person_id are nulls (Each model has its own ID, but not the associated model's id). I also should note there are no validation errors and the data is good.
The library has a bug in the build function as far as I can tell. Same syntax with create worked perfectly.
In Sequelize v6, the association identifier in the include section is not valid. Otherwise, this build function should properly work.
I have a model with 3 entities, Documents, Employees and Managers. A Document belongs to and Employee and an Employee belongs to a Manager.
My objective is to retrieve all the documents of a manager employees.
My piece of code is working
Document.findAll({
include: [{
model: models.Employee,
required: true,
as: 'employee',
include: [{
model: models.Manager,
required: true,
as: 'manager',
}],
}],
But I'm not really satisfied, I would like to have my where condition outside my include but when I try
where {
'employee.manager.id': id
}
an error is raised.
Is it possible to setup a where condition outside the includes ?
EDIT :
I changed my code to
Document.findAll({
where: {'$employee.manager.id$': id},
include: [{
model: models.Employee,
required: true,
as: 'employee',
include: [{
model: models.Manager,
required: true,
as: 'manager',
where: { id: managerId },
}],
}],
and it's working.
Now, I would like to refine my model. Each document as a type (administrative, evaluation ...) and I would like to retrieve the most recent document for each type for each manager. I used an order by which is working fine and tried to use a group by which is not working
order: [ [ 'updatedAt', 'DESC' ]],
group: ['type'],
I get the following message : column \"Document.id\" must appear in the GROUP BY clause or be used in an aggregate function.
Any idea what I'm doing wrong ?
Yes, you can do that ,
Issue with current one :
where {
'employee.manager.id': id // <--- Here 'employee.manager.id' cosidered as whole column name
}
Solution :
where {
'$employee.manager.id$': id //<--- To make sequlize realize you need to place it b/w $$ ,
//or
sequelize.col('employee.manager.id') : id // <--- You can try this also
}
I'll preface this by saying I think my model associations may be incorrect
Basically what i'm trying to do is is return an array of all Employees for a company.
Get all locations that have the same companyId
With those locations, get all Profiles tied to the locationId.
Profiles are linked to Locations through Employees.
Below is my code.
The query:
Location.findAll({
where: { companyId: user.profile.companyId },
include: [
{
model: Employee
}
]
})
This generates the error "employee is not associated to location!".
My models:
Employee.belongsTo(Profile)
Employee.belongsTo(Location)
Profile.belongsTo(Company)
Location.belongsTo(Company)
Profile.belongsToMany(Location, {
through: Employee,
foreignKey: "profileId"
})
Location.belongsToMany(Profile, {
through: Employee,
foreignKey: "locationId"
})
EDIT:
Adding Location.hasMany(Employee) allows me to do the query however it still requires a for loop within another for loop to get the correct data structure needed.
const locations = await models.Location.findAll({
where: { companyId: user.profile.companyId },
include: [{ model: models.Profile }]
})
const response = []
locations.forEach(location => {
location.profiles.forEach(profile => {
response.push({ location, profile })
})
})
return response
The query below returns what exactly as is however its only for a single location. I need to run the same query but for multiple locations.
Employee.findAll({ where: { locationId }, include: [Profile, Location] })
You've specified that Location belongsToMany Locations, but you've failed to specify the other way around. You should specify that a Location hasMany Employees.
Specifying Employee.belongsTo(Location) allows you to include related Locations with Employees. Specifying Location.hasMany(Employee) allows you to include related Employees with Locations.
I was able to recreate your problem and fix it with this line.
Location.hasMany(Employee, {
//...
})
i have made two foreign keys from user table.
db.Subscription.belongsTo(db.User, {foreignKey: 'creatorId'});
db.Subscription.belongsTo(db.User, {foreignKey: 'subscriberId'});
during search query i get subscriberId column included instead of creatorId
Subscription.findAll({
where: {
subscriberId: req.decoded._id
},
include: [
{
model: User,
foreignKey: 'creatorId',
attributes: ['name', 'role', 'uid', 'imageUrl']
}
]
})
can someone please find out what i am doing wrong here.
Try setting a name for the associations so Sequelize has a better idea which association to include. You can do something like this to set the names on the associations...
db.Subscription.belongsTo(db.User, {
as: 'creator',
foreignKey: 'creatorId'
});
db.Subscription.belongsTo(db.User, {
as: 'subscriber',
foreignKey: 'subscriberId'
});
Then you can use those names in the query to get the specific association, as so...
Subscription.findAll({
include: {
model: User,
as: 'creator',
attributes: ['name', 'role', 'uid', 'imageUrl']
},
where: {
subscriberId: req.decoded._identer
}
});
When you have associations to the same table more than once setting a name helps the ORM determine which association to load. For the record, for all of the records that get returned you can access that association by accessing the .creator on the instance.
Good luck! :)