I have following models defined for Sequelize
const brand = sequelize.define('brands', {
brand_id: {
type: Sequelize.UUID,
primaryKey: true
},
name: Sequelize.STRING
}, {
timestamps: false,
underscored: true
});
const model = sequelize.define('models', {
model_id: {
type: Sequelize.UUID,
primaryKey: true
},
name: Sequelize.STRING
}, {
timestamps: false,
underscored: true
});
model.belongsTo(brand, {foreignKey: 'brand_id'});
I use this code to query the database
model.findOne({
raw: true,
where: {name: 'TIPO'},
include: brand})
.then(car => {
console.log(car);
});
And it returns the rows formatted like this
{ model_id: '7e5a29ba-05b1-45f7-9fee-41f8440fe975',
name: 'TIPO',
brand_id: 'f3e4962c-906f-46c4-b992-7375ab46002a',
'brand.brand_id': 'f3e4962c-906f-46c4-b992-7375ab46002a',
'brand.name': 'FIAT' }
While I would really like for it to look more like this
{ model_id: '7e5a29ba-05b1-45f7-9fee-41f8440fe975',
model_name: 'TIPO',
brand_name: 'FIAT' }
Is there a way to achieve make it work like this?
You need to use the attributes option
model.findOne({
raw: true,
where: {name: 'TIPO'},
include: {
model: model.brand,
attributes: [ 'name' ], // represents brand.name
}
})
.then(car => {
const newCar = car;
// if you want to change/rename the object properties around, do so here via newCar
console.log(newCar);
});
You may want to consider changing "name" in the brand model to bName or brandName to avoid confusion.
You need to add attributes to both model and brand... without these, you will see all fields (e.g. SELECT *). Take a look at the doc
Related
I have a Lessons table:
const Lesson = sequelize.define(
'lesson',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
date: { type: DataTypes.DATEONLY },
title: { type: DataTypes.STRING },
status: { type: DataTypes.INTEGER }
},
{ underscored: true, timestamps: false }
);
Which has a belongsToMany relationship to the Teachers table
const Teacher = sequelize.define(
'teacher',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true
},
name: { type: DataTypes.STRING }
},
{ underscored: true, timestamps: false }
);
I am trying to make a request to get a list of lessons:
const data = await Lesson.findAll({
include: [
{ model: Teacher, through: { attributes: [] } }
],
order: [['id', 'DESC']],
limit,
offset
});
How to make a limit on the number of teachers, that is, to accept only those lessons where the number of teachers belonging to this lesson, for example, is equal to 3.
Your query will form something like this
const data = await Lesson.findAll({
include: [
{
model: Teacher,
required: true
attributes: [[Sequelize.literal('(count(teacher.id))')], 'teacherCount']
}
],
order: [['id', 'DESC']],
having: {teacherCount: {[Op.gt]: 3}}
limit: 10,
offset: 0
});
This is still a raw code you might want to do some trial and error on your own, basically you want to do a join and get count of teachers from that join once you have it you just want to filter your query using that virtual column you just created.
P.S: Above code will maybe throw group by error for that you will need to add a group: ['column_name'] so that non-aggregated columns come along in aggreation query
Reference for group by:
https://www.postgresqltutorial.com/postgresql-group-by/
Lastly, I would suggest writing a raw query first and then finding ways to convert that into sequelize that way you know what is the exact issue that you are facing.
I'm using NodeJS and sequelize.
I have user table with a column named duel_id, and each user can be assigned to one duel at a time.
Each duel can have multiple users in it.
I have the following User Model:
const User = Model.define(
'User',
{
user_id: {
type: DataType.INTEGER,
primaryKey: true,
},
username: {
type: DataType.STRING(255),
},
character: {
type: DataType.INTEGER,
},
duel_id: {
type: DataType.INTEGER,
},
},
{
indexes: [{ fields: ['user_id', 'username'] }],
tableName: 'users',
timestamps: false,
},
);
User.hasOne(Duel, { as: 'duel', foreignKey: 'id', sourceKey: 'duel_id' });
with the following Duel model:
const Duel = Model.define(
'DuelRoom',
{
id: {
type: DataType.INTEGER,
primaryKey: true,
autoIncrement: true,
},
round_id: {
type: DataType.INTEGER,
},
status: {
type: DataType.STRING,
},
turn_of_user_id: {
type: DataType.INTEGER,
},
winner: {
type: DataType.STRING,
},
},
{
indexes: [{ fields: ['id'] }],
tableName: 'duel_rooms',
timestamps: true,
},
);
The above code works and return the user and the associated duel if he has one.
I want also to return all the users associate to the same duel.
I tried to connect the relationship with hasMany/ belongsTo with no success. The following errors appears:
Error: DuelRoom.hasMany called with something that's not a subclass of Sequelize.Model
I want to be able to query to get the data like this:
user: {
user_id,
username
duel: {
round_number
players: [{user_id, username}]
}
}
Get the current user with the duel info, with all players associated with the same duel_id as an array named players.
Any idea of how I can define such a relation using sequelize to return all users associated to the user duel?
If a User model has dual_id then you should use belongTo from User to DualRoom instead of hasOne:
User.belongsTo(Duel, { as: 'duel', foreignKey: 'duel_id' });
If you wish to have users collection in a Duel model then this will work with the following hasMany:
Duel.hasMany(User, { as: 'users', foreignKey: 'duel_id' });
Take into account that you should register all associations AFTER all model registrations like I advised in this answer
After all this setup you can get what you wish by executing a query like this:
const user = await User.findOne({
where: {
user_id: id
},
include: [{
model: Duel,
as: 'duel',
include: [{
model: User,
separate: true,
as: 'users'
}]
}]
})
As each user can have a duel and one duel can be associated with many users. It is a one-to-many association, so, you should try:
Duel.hasMany(User);
User.belongsTo(Duel);
I'm getting images is not associated to product! error while binding the association of the model.
ProductImages is associated to Product and ProductImages is associated to Images model. So, i need to render images property into products collection by assigning to it.
The model that i'm trying to bind is as below.
products.model.ts
const Product = SQLize.define('product', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }
product_title: { type: new DataTypes.STRING(255) },
vendor_id: { type: DataTypes.INTEGER }
});
Product.hasMany(ProductImages, {foreignKey: 'product_id', targetKey: 'id', as :'product_img_refs'})
export { Product };
product-images.model.ts
const ProductImages = SQLize.define('product_images', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
product_id: { type: DataTypes.INTEGER },
product_image_id: { type: DataTypes.INTEGER }
img_type_id: { type: DataTypes.INTEGER }
});
ProductImages.belongsTo(ImagesModel, {foreignKey: 'product_image_id', targetKey: 'id', as:'product_images' })
export {ProductImages}
images.model.ts:
const ImagesModel = SQLize.define('images', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
img_url: { type: DataTypes.STRING }
});
export { ImagesModel }
Below is the repository file on which i have performed the SQLize operation.
public async getProductData() {
var prodData = Product.findAll({
include: [
{ model: Vendor, as: 'vendor' },
{ model: ProductImages, as: 'product_img_refs' }
{ model: ImagesModel, as: 'product_images' }
]
});
return prodData;
}
=> Sample product_images table records.
=> Sample images table records.
=> DB Schema for more visualisation.
=> I have checked this answer but it is not relevant to my model as i have three models with different association.
Instead of both a hasMany and a belongsTo relationship, create a many-to-many relationship on Product to Images and also one from Images to Product.
You can extend the auto-generated table (with ProductId and ImageId columns) by passing the name of a model.
const ProductImages = SQLize.define('ProductImages', {
// ...
});
Product.belongsToMany(ImagesModel, { through: ProductImages });
ImagesModel.belongsToMany(Product, { through: ProductImages });
You can now do:
await Product.getImages();
await Images.getProducts();
Or use the include option while querying. There are examples in the documentation here. It'll be something like:
await Product.findAll({
include: ImagesModel,
});
// It will be nested as such:
// {
// fields from product
// Images: {
// fields from image
// ProductImages: {
// fields from the 'through' table
// }
// }
// }
I have 3 tables:
Job post
recruitment phase
Interview slot
Their association is,
jobpost has many recruitment phases and
recruitment phase has many interview slots
I am able to get all the recruitment phases of a job post by including the recruitmentphase association and group clause.
const jobPosts = await JobPost.unscoped().findAll({
where,
include: [
{
model: db.RecruitmentPhase,
include: [{
model: db.InterviewSlot,
},
],
group: ['RecruitmentPhases.id'],
});
But I am only getting one interview slot for the recruitment phase, event though there are many interviewslots for that recruitment phase.
I tried to do group clause inside include.
const jobPosts = await JobPost.unscoped().findAll({
where,
include: [
{
model: db.RecruitmentPhase,
group: ['InterviewSlots.id'],
include: [{
model: db.InterviewSlot,
},
],
group: ['RecruitmentPhases.id'],
});
but it also giving only one interview slot
EDIT
jobpost model :
module.exports = (sequelize, DataTypes) => {
const jobPost = sequelize.define('JobPost', {
id: {
type: DataTypes.BIGINT,
allowNull: true,
autoIncrement: true,
primaryKey: true,
},
jobTitle: {
type: DataTypes.STRING(150),
allowNull: true,
},
}, {
timestamps: true,
defaultScope: {
attributes: { exclude: ['createdAt', 'updatedAt'] },
},
});
jobPost.associate = (models) => {
jobPost.hasMany(models.RecruitmentPhase);
};
return jobPost;
};
Recruitment phase model :
module.exports = (sequelize, DataTypes) => {
const recruitmentPhase = sequelize.define('RecruitmentPhase', {
id: {
type: DataTypes.BIGINT,
allowNull: true,
autoIncrement: true,
primaryKey: true,
},
phaseName: {
type: DataTypes.STRING(200),
allowNull: true,
},
}, {
timestamps: true,
});
recruitmentPhase.associate = (models) => {
recruitmentPhase.belongsTo(models.JobPost);
recruitmentPhase.hasMany(models.InterviewSlot);
};
return recruitmentPhase;
};
Interview slot model :
module.exports = (sequelize, DataTypes) => {
const interviewSlot = sequelize.define('InterviewSlot', {
id: {
type: DataTypes.BIGINT,
allowNull: true,
autoIncrement: true,
primaryKey: true,
},
interviewDate: {
type: DataTypes.DATE,
allowNull: true,
},
});
interviewSlot.associate = (models) => {
interviewSlot.belongsTo(models.RecruitmentPhase);
};
return interviewSlot;
};
Remove group: ['RecruitmentPhases.id'], in order to see the details of InterviewSlots. As is, you're seeing a summary of interview slots...
So, I am not quite sure about, this is the correct answer and is the correct way to do it.
But When I do the grouping for that nested table 'interviewslot' with this '->' , It worked.
group: ['RecruitmentPhases.id', 'RecruitmentPhases->InterviewSlots.id'],
Why do you seem to be fetching JobPost instead of RecruitmentPhase if that is what you are looking to fetch from the database?
From what I understand - you are looking to fetch all the RecruitmentPhases with their job post accompanying the InterviewSlots allocated for each RecruitmentPhase.
Possible Code:
(Updated)
const rec = await db.RecruitmentPhase.findAll({
include:[{model: db.JobPost, where:{ id:job_id }}, {model: db.InterviewSlot}]
});
res.json(rec)
//Expected JSON Data
{
"RecruitmentPhase":[
{
"id":1,
"phaseName":"Phase 1",
"JobPosts": {
"id":1,
"jobTitle":"XYZ"
},
"InterviewSlots":[
{//inteview slot #1 data},
{//interview slot #2 data}
]
},
{
"id":2,
"phaseName":"Phase 2",
"JobPosts": {
"id":1,
"jobTitle":"XYZ"
},
"InterviewSlots":[
{//inteview slot #1 data},
{//interview slot #2 data}
]
}
]
}
I have 2 models users and tags, both are associated through another model called usersTags and all 3 models have paranoid set with custom timestamps. I understand that associating models will add additional methods to work on the associations to all associated models, so i am wanting to making a simple setTags call for users, the docs shows that if in the array in the method does not contain the element that is stored in the database it should be removed, otherwise it should be created/restored.
So i try to restore a previously removed tag but for some reason it fails. The models are defined as following:
Users
module.exports = function(sequelize, DataTypes) {
const Users = sequelize.define("users", {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING(100),
allowNull: false,
validate: {
len: {
args: [3, 100],
msg: "String length is not in this range"
}
}
},
password: {
type: DataTypes.STRING(100),
allowNull: false,
field: "password_hash"
}
}, {
tableName: "users",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true
});
Users.associate = function(models) {
// Add this association to include tag records
this.belongsToMany(models.tags, {
through: {
model: models.usersTags,
unique: true
},
foreignKey: "users_id",
constraints: false
});
};
return Users;
};
Tags
module.exports = function(sequelize, DataTypes) {
const Tags = sequelize.define("tags", {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(45),
allowNull: false
}
}, {
tableName: "tags",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true
});
Tags.associate = function(models) {
this.belongsToMany(models.users, {
through: {
model: models.usersTags,
unique: true
},
foreignKey: "tags_id",
constraints: false
});
};
return Tags;
};
usersTags
module.exports = function(sequelize, DataTypes) {
const UsersTags = sequelize.define("usersTags", {
users_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: "users",
key: "id"
}
},
tags_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: "tags",
key: "id"
}
}
}, {
tableName: "users_tags",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true,
indexes: [
{
unique: true,
fields: ["users_id", "tags_id"]
}
]
});
return UsersTags;
};
Test
let _user;
models.users.findOne({where: {id: 100}})
.then(user => {
_user = user;
return _user.setTags([1]); // Successfully create association tag with id 1
})
.then(() => _user.setTags([])) // Successfully remove all associated tags
.then(() => _user.setTags([1])); // Should restore association tag with id 1 but fails
Executed query
app:database Executing (default): SELECT `id`, `username`, `first_name`, `last_name`, `birthday`, `description`, `location`, `email`, `type`, `image_path` FROM `users` AS `users` WHERE ((`users`.`delete_time` > '2018-08-28 19:40:15' OR `users`.`delete_time` IS NULL) AND `users`.`id` = 100); +0ms
app:database Executing (default): SELECT `users_id`, `tags_id`, `create_time`, `update_time`, `delete_time` FROM `users_tags` AS `usersTags` WHERE ((`usersTags`.`delete_time` > '2018-08-28 19:40:15' OR `usersTags`.`delete_time` IS NULL) AND `usersTags`.`users_id` = 100); +6ms
app:database Executing (default): INSERT INTO `users_tags` (`users_id`,`tags_id`,`create_time`,`update_time`) VALUES (100,1,'2018-08-28 19:40:15','2018-08-28 19:40:15'); +7ms
For some reason the tag search query is failing to retrieve the tag that contains the delete_time set and therefore the last query is insert instead of update, i know the workaround would be to set paranoid to false but i have to keep track of all activities, i know another workaround would be to create a custom model method to handle this but i still want to know if there is a way to achieve this without having to create an additional custom method
your code in not in a correct async order so your _user global variable is not initiated,I think this is the correct order :
let _user;
models.users.findOne({where: {id: 100}})
.then(user => {
_user = user;
_user.setTags([]).then(()=>{
_user.setTags([1])
})
})