Sequelize - include all association filtered results - node.js

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

sequelize select attributes from the junction table

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.

Sequelize: Bind multiple models for join query and create custom columns

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 }
]
});

Sequelize: <table> is not associated to <Table>

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
// }
// }
// }

subquery Sequelize with nested query (include)

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

How to add array of ids in a query with many to many relationships Sequelize

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']
}]
});

Resources