I have a a model called Post and Country. When I filter by a particular country Id, it returns correctly filtered posts but only returns that specific country in the response and discards all the other countries associated with the Post. How can I include and retain all the countries associated with the post in the response?
Post.js
class Post extends Sequelize.Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
number: {
type: Sequelize.INTEGER,
validate: {
isInt: true
}
},
}
);
static associate(models) {
this.myAssociation = this.belongsToMany(models.Country,
{through: "CountriesImpacted", foreignKey: "id"});
}
}
module.exports = Incident;
Country.js
class Country extends Sequelize.Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
primaryKey: true,
unique: true
}
}, {sequelize, modelName: 'country', tableName: 'Countries'}
)
}
}
module.exports = Country;
routes.js
Post.findAndCountAll({
attributes: { exclude: ["createdAt", "updatedAt"] },
order: [Country, "name", "asc"],
include: [{
model: Country,
where: { id: country_ids_from_request }
}],
where: filters,
distinct: true,
offset: offset,
limit: limit
})
example output
{
'0': {
id: 1,
number: 1203021,
countries: [
{
id: 6,
name: 'Australia',
CountriesImpacted: {
id: 1,
countryId: 6
}
},
{
id: 7,
name: 'New Zealand',
CountriesImpacted: {
id: 1,
countryId: 7
}
}
]
}
}
when I filter by country id 6, the repsonse will discard New Zealand...
It requires a sub-query to fetch the eligible Post model. It will look like following
return Post.findAll({
include: [
{
model: Country
}
],
where: {
id : {
[Sequelize.Op.in] : [Sequelize.literal(`(SELECT posts.id FROM posts INNER JOIN countries ON countries.postId = posts.id WHERE ${COUNTRY_ID} IN (countries.id))`)] // Subquery...
}
}
})
Here i assume following
Your Post schema name is posts.
Your Country schema name is countries.
Country has a postsId foreign key REFERENCES TO Post.
If you are not comfortable with having sub-query inside your project while using Sequelize (ORM), then you have to execute two query, first fetching all the eligible Posts then fetch desired for those Posts.
Related
I have been searching for a solution for hours now without hope.
I have Many-to-Many relationship with Student and Classes through a Junction Table Enrollment.
Everything is working fine and I am able to insert and retrieve data.
However, I don't want all the fields from the junction table to be returned in the result. Only selected attribute.
First, the below is my code:
Models definition and associations:
const Student = sequelize.define(
"Student",
{ firstName: Sequelize.STRING },
{ timestamps: false }
);
const Class = sequelize.define(
"Class",
{ className: Sequelize.STRING },
{ timestamps: false }
);
const Enrollment = sequelize.define(
"Enrollment",
{
enrollmentType: {
type: DataTypes.STRING,
},
},
{ timestamps: false }
);
Student.belongsToMany(Class, { through: Enrollment });
Class.belongsToMany(Student, { through: Enrollment });
The query:
return Student.findOne({
where: { id: 2 },
include: [
{
model: Class,
attributes: ["className"],
},
],
raw: true,
nest: true,
});
And I got the below result:
{
id: 2,
firstName: 'John',
Classes: {
className: 'Chemistry',
Enrollment: { enrollmentType: 'Normal student', StudentId: 2, ClassId: 1 }
}
}
I am not interested in the repeated StudentId and ClassId returned from the Enrollment table.
Someone suggested that I use through:
return Student.findOne({
where: { id: 2 },
include: [
{
model: Class,
attributes: ["className"],
through: { attributes: ["enrollmentType"] },
},
],
raw: true,
nest: true,
});
But now I am getting the same result, only the order of fields seems to be different
{
id: 2,
firstName: 'John',
Classes: {
className: 'Chemistry',
Enrollment: { ClassId: 1, StudentId: 2, enrollmentType: 'Normal student' }
}
}
How can I only return the enrollmentType without other Enrollment fields?
I am using #Emma response in the comment as the solution to my issue.
This is absolutely correct. I created a simple ExpressJS API which returned the results accurately and fully.
As Emma mentioned, it is a console limitation.
See her full reply in the comments on the question.
I'd like to apply a join and groupBy in Sequelize v5 that will fetch the records from five models tables and return the records as below format.
{
"data": [
{
"products": {
"id": "01",
"name": "lorium",
"description": "ipsum",
"product_images": [
{
"url": "", // From images tbl
"image_type": "Front" // From imge_types tbl
},
{
"url": "",
"image_type": "Back"
}
]
},
"vendor": {
"first_name": "john",
"last_name": "doe"
}
}
]
}
I have created separate all five models and assign association to them.
Product Model::
const Product = SQLize.define('product', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, }
product_title: { type: new DataTypes.STRING(255) },
vendor_id: { type: DataTypes.INTEGER.UNSIGNED }
});
Product.hasMany(ProductImages, {foreignKey: 'product_id', targetKey: 'id', as :'product_img_refs'})
export { Product };
Vendor Model::
const Vendor = SQLize.define('vendor', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
first_name: { type: DataTypes.STRING(100) },
last_name: { type: DataTypes.STRING(100) }
});
Product.belongsTo(Vendor, {foreignKey: 'id'})
Vendor.hasOne(Product, {foreignKey: 'id'})
export { Vendor }
Product Images Model::
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 }
});
export {ProductImages}
Images Model::
const ImagesModel = SQLize.define('images', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
img_url: { type: DataTypes.STRING }
});
export { ImagesModel }
Image Types Model::
const ImageTypes = SQLize.define('image_types', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
image_type: { type: DataTypes.STRING }
});
export { ImageTypes }
Below is the repository file on which i have performed the SQLize operation: Updated::
public async getProductData() {
var prodData = Product.findAll({
include: [
{ model: Vendor, as: 'vendor' },
{ model: ProductImages, as: 'product_img_refs' }
]
});
return prodData;
}
I am not getting the correct way to bind the all models that will return me a result as described in the above json format.
To get the nested output as shown in the question, you would need to create associations between the following:
ProductImages and ImagesModel
ProductImages and ImageTypes
Once done, you can nest the models in the findAll options as shown below:
// Create associations (depends on your data, may be different)
ProductImages.hasOne(ImagesModel);
ProductImages.hasOne(ImageTypes);
// Return product data with nested models
let prodData = Product.findAll({
include: [
{ model: Vendor, as: 'vendor' },
{
model: ProductImages, as: 'product_img_refs',
include: [
{ model: ImagesModel }, // Join ImagesModel onto ProductImages (add aliases as needed)
{ model: ImageTypes } // Join ImageTypes onto ProductImages (add aliases as needed)
]
}
]
});
I found the issue. you were trying to include ProductImages model into Vendor. As per your association, ProductImages associate with Product not with Vendor.
So please try this
let prodData = Product.findAll({
include: [
{ model: Vendor, as: 'vendor' },
{ model: ProductImages }
]
});
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 this two models :
const Category = sequelize.define('Category', {
name: {
type: DataTypes.STRING,
unique: true
},
description: {
type: DataTypes.STRING,
},
status: {
type: DataTypes.BOOLEAN,
default: true
},
image: DataTypes.STRING,
}, {});
Category.associate = function (models) {
Category.hasMany(models.Product)
Category.hasMany(models.Category, { as: 'child', foreignKey: 'ParentId' });
Category.belongsTo(models.Category, { as: 'parent', foreignKey: 'ParentId' });
};
return Category;
const Product = sequelize.define('Product', {
name: { type: DataTypes.STRING, unique: true },
description: { type: DataTypes.STRING },
price: { type: DataTypes.INTEGER },
status: {
type: DataTypes.BOOLEAN,
default: true
},
image: DataTypes.STRING,
}, {});
Product.associate = function (models) {
Product.belongsTo(models.Category)
};
return Product;
nothing fancy here .my category model can has a CategoryId for nested purposes.so my question is is there a way I can have a subquery in Category model that select products with a related category Id?
Category.findAll({
limit: page_size,
where: {
ParentId: {
[sequelize.Op.eq]: null
}
},
include: [{
model: Category,
as: "child",
attributes: [
[sequelize.literal('(SELECT * FROM "Products" WHERE "CategoryId" = "child"."id")'),
'products'],
],
group: ['products']
}],
offset: offset_value,
order: [
['id', 'DESC'],
],
})
I tried to include and in the include section I need to query to my products that group it as a field.
p.s: if category instance has no CategoryId it will be considered as
Parent.
and I wrote this query and tested it comes with this error:
subquery must return only one column
I have 2 entities and one for n:m relationship:
const Item = db.define('item', {
id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true,
},
title: Sequelize.STRING,
description: Sequelize.STRING,
price: {
type: Sequelize.FLOAT,
defaultValue: 0.0,
},
});
const Category = db.define('category', {
id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true,
},
title: Sequelize.STRING,
});
const ItemCategory = db.define('item_category', {
id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true,
},
category_id: {
type: Sequelize.BIGINT
},
item_id: {
type: Sequelize.BIGINT
}
});
And relations:
Category.belongsToMany(Item, {
through: {
model: ItemCategory,
unique: false
},
foreignKey: 'category_id',
constraints: false
});
Item.belongsToMany(Category, {
through: {
model: ItemCategory,
unique: false
},
foreignKey: 'item_id',
constraints: false
});
Association is working fine(I guess). But when I try to query Item, the result comes without categories field.
Also I can add include option and it returns category objects:
Item.findAll({ include: [{ model: Category }] })
The QUESTION IS: How to associate ONLY categories_ids when querying Item objects to have something like this in response:
{
id: 1,
categories_ids: [1,3,4],
price: 20
}
You can't actually do that because of how data of nested associations are arranged by default.
Suppose you did
Item.findOne({
where: { item_id: 1},
include: [{
model: ItemCategory,
as: 'categories_ids',
attributes: ['category_id']
}]
})
You will get
{
id: 1,
categories_ids: [
{ category_id: 1 },
{ category_id: 2 },
{...},
{...}
],
price: 20,
}
Of which you can probably re-arrange the information, which involves the process of something like this:
let pojo = JSON.parse(JSON.stringify(data))
pojo.categories_ids.forEach(function(el, index) {
pojo.categories_ids[index] = el.category_id
})
Try something like this
associate your through model directly to item as well so you can include in query
Item.hasMany(ItemCategory, {
foreignKey: 'item_id',
constraints: false
});
Item.findAll({
include: [{
model: ItemCategory,
as: 'categories_ids',
attributes: ['category_id']
}]
});