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 }
]
});
Related
I've been tried of making relation of one to many on sequelize, i just wanna make the "jadwal" data have data of "dosen".
This's the JadwalModel that have the foreignKey
// Main Folder > models > JadwalModel.js
import { Sequelize } from "sequelize";
import db from "../config/Database.js";
import Dosen from "./DosenModel.js";
const { DataTypes } = Sequelize;
const Jadwal = db.define('jadwal', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
id_dosen: {
type: DataTypes.INTEGER
},
room: {
type: DataTypes.STRING
},
date: {
type: DataTypes.DATE
}
}, {
freezeTableName: true
});
// Dosen.hasMany(Jadwal, { foreignKey: 'id_dosen' });
// Jadwal.belongsTo(Dosen, { foreignKey: 'id_dosen' }); // This return ReferenceError: Cannot access 'Dosen' before initialization
export default Jadwal;
This's the Dosen model that the primaryKey/ ReferenceKey
// Main Folder > models > DosenModel.js
import { Sequelize } from "sequelize";
import db from '../config/Database.js';
import Jadwal from "./JadwalModel.js";
const { DataTypes } = Sequelize;
const Dosen = db.define('dosen', {
id_dosen: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
id_matkul: {
type: DataTypes.INTEGER
},
id_user: {
type: DataTypes.INTEGER,
},
name: {
type: DataTypes.STRING
},
nip: {
type: DataTypes.INTEGER
},
username: {
type: DataTypes.STRING
},
password: {
type: DataTypes.STRING
},
refresh_token: {
type: DataTypes.TEXT
}
}, {
freezeTableName: true
});
// Dosen.hasMany(Jadwal, { foreignKey: 'id_dosen' });
// Jadwal.belongsTo(Dosen, { foreignKey: 'id_dosen' });
export default Dosen;
This's the jadwal controller the file to get the data
// Main Folder > controllers > Jadwal.js
import Dosen from '../models/DosenModel.js';
import Jadwal from '../models/JadwalModel.js';
export const getJadwals = async(req, res) => {
try {
const jadwal = await Jadwal.findAll({
attributes: ['id', 'id_dosen', 'room', 'date']
}, {
include: [{ model: Dosen }]
});
res.status(200).json(jadwal);
} catch (error) {
console.log(error);
}
}
And this's the example result
// Result
[
{
"id": 1,
"id_dosen": 1,
"room": "Ruang TR 2",
"date": "2022-05-26T10:00:00.000Z"
}
]
// The result that i want is
[
{
"id": 1,
"dosen": {
"name" : "dosenName"
},
"room": "Ruang TR 2",
"date": "2022-05-26T10:00:00.000Z"
}
]
That wasn't error, i just forgot because i only fetch several field not all so i decide to delete the attributes and get the data relation
attributes: ['id', 'id_dosen', 'ruangan', 'tanggal']
I want to count the number of likes when I fetch the post details.
User Model
User.init(
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
userName: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
field: 'user_name',
},
}
);
static associate(models) {
// User has many posts --> One-to-Many Relationship
this.hasMany(models.Post, {
foreignKey: 'userId',
});
// Likes relationship
this.belongsToMany(models.Post, {
through: 'PostLikes',
foreignKey: 'userId',
as: 'likes',
});
}
And here is the Post Model
Post.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
defaultValue: DataTypes.UUIDV4,
},
body: {
type: DataTypes.STRING,
allowNull: false,
},
}
);
static associate(models) {
// User-Post Relationship
this.belongsTo(models.User, {
foreignKey: 'userId',
});
// Likes Relationship
this.belongsToMany(models.User, {
through: 'PostLikes',
foreignKey: 'postId',
as: 'likes',
});
}
So, it now creates a joined table PostLikes and I am trying query that fetches the post along with the likes and number of likes on that post.
const postsIdCol = '"likes->PostLikes"."postId"';
const usersIdCol = '"likes->PostLikes"."userId"';
const postCol = '"Post"."id"';
const response = await Post.findOne({
where: {
id: postid,
},
// includeIgnoreAttributes : false,
attributes: [
'id', 'body', 'createdAt',
[sequelize.literal(`COUNT(${postsIdCol}) OVER (PARTITION BY ${postCol})`), 'likesCount'],
],
include: [
{
model: Comment, ----> Post is also associated with comments, ignore this
as: 'comments',
attributes: ['id', 'content', 'createdAt', 'updatedAt'],
},
{
model: User,
as: 'likes',
through: {
attributes: [],
},
attributes: ['id', 'userName'],
},
],
});
return response;
The Response I am getting on doing this query is like this :
{
"data": {
"id": "182d6377-5bf6-4b65-9e29-cb79acc85c0a",
"body": "hoping for the best",
"likesCount": "6", -----> this should be 3
"likes": [
{
"id": "1af4b9ea-7c58-486f-a37a-e46461487b06",
"userName": "sdfbsd",
},
{
"id": "484202b0-a6d9-416d-a8e2-6681deffa3d1",
"userName": "ndnadonfsu",
},
{
"id": "b3c70bee-e839-4449-b213-62813af031d1",
"userName": "difniandca",
}
]
}
}
You need another PARTITION BY column from the other association than the one that you want to count.
For example, if you want to count the likes, you need to partition by parent id (Post.id) and other association id (Comment.id).
If you want to count the comments, you need to partition by parent id (Post.id) and other association id ("likes->PostLikes"."UserId").
[Sequelize.literal(`COUNT(${postsIdCol}) OVER (PARTITION BY ${postCol}, "Comments"."id")`), 'likesCount']
Where it says "Comments", you need to add your comment table name.
I have two tables Employee and Department
Department
const Department = Sequelize.define(
"Department",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
underscored: true,
timestamps: true,
paranoid: true,
modelName: "Department",
tableName: "departments",
},
);
Department.associate = function (models) {
// associations can be defined here
models.Department.hasMany(models.Employee, {
foreignKey: "department_id",
as: "employees",
});
};
return Department;
};
Employee
const Employee = Sequelize.define(
"Employee",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
status: {
type: DataTypes.STRING,
defaultValue: "active",
},
departmentId: {
type: DataTypes.INTEGER,
},
},
{
underscored: true,
timestamps: true,
modelName: "Employee",
tableName: "employees",
},
);
Employee.associate = function (models) {
models.Employee.belongsTo(models.Department, {
foreignKey: "department_id",
as: "department",
});
};
return Employee;
};
Now I have to fetch the list of employees and putting a filter of department_id = 1
const { departmentId } = req.body;
const employees = await Employee.findAll({
include: [
{
model: Department,
where: {
id: departmentId,
},
},
],
});
I am getting the issue. Department is mapped by association "departments"
Cannot fetch the data.
I found the answer on sequelize docs
const employees = await Employee.findAll({
include: [
{
association: "department", // this is the place to change
where: {
id: departmentId,
},
},
],
});
Learnings:
We will not be able to put association and model together.
We will be able to use the Model if no association is there.
We will be able to use association if there is one.
References: https://sequelize.org/master/manual/eager-loading.html#:~:text=You%20can%20also%20include%20by%20alias%20name%20by%20specifying%20a%20string%20that%20matches%20the%20association%20alias
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.
In my project, I have Users and Groups, and a User can have multiple Groups. I'm having trouble filtering a list of users based on those who are in a certain group.
Here's my setup:
User Model
module.exports = (sequelize) => {
const model = sequelize.define('users',
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
field: 'uID',
},
username: {
type: Sequelize.STRING, allowNull: false, unique: true, field: 'uEmail',
},
}, {
timestamps: false,
tableName: 'Users',
});
model.modelName = 'users';
return model;
};
Group Model
module.exports = (sequelize) => {
const model = sequelize.define('groups', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
field: 'gID',
},
name: { type: Sequelize.STRING, allowNull: false, field: 'gName' },
description: { type: Sequelize.STRING, allowNull: false, field: 'gDescription' },
}, {
timestamps: false,
tableName: 'Groups',
});
model.modelName = 'groups';
return model;
};
M2M Through Table :: UserGroups
module.exports = (sequelize) => {
const model = sequelize.define('usergroups', {
}, {
timestamps: false,
tableName: 'UserGroups',
});
model.modelName = 'usergroups';
return model;
};
Association
User.belongsToMany(Group, {
through: UserGroup,
as: 'groups',
foreignKey: 'uID',
});
Group.belongsToMany(User, {
through: UserGroup,
as: 'users',
foreignKey: 'gID',
});
My db Users look like this (in shorthand):
User1.groups = [1,2,3]
User2.groups = [1,3]
User3.groups = [2,3]
I'm building the search rest API: GET /users?groups[]=1&groups[]=2
In building the query, I've tried to filter based on the groups passed in using this format:
let query = {};
query.include = [
{
model: Group,
as: 'groups',
where: {id: {[Op.in]: filter.groups}},
attributes: ['id', 'name'],
},
];
const result = yield User.findAndCountAll(query);
or tried using through (with required: true or required: false):
let query = {};
query.include = [
{
model: Group,
as: 'groups',
through: { where: {'gID': {[Op.in]: filter.groups}}, required: true},
attributes: ['id', 'name'],
},
];
const result = yield User.findAndCountAll(query);
It kinda works, except the returned objects associated groups are abbreviated. If I pass in this filter:
GET /users?groups[]=1
The response comes back with the right users, but the groups associated with those users are filtered:
[
{
"id": 1,
"username": "user1#site.com",
"groups": [
{
"id": 1,
"name": "My Group",
"usergroups": {
"uID": 1,
"gID": 1
}
}
]
},
{
"id": 2,
"username": "user2#site.com",
"groups": [
{
"id": 1,
"name": "My Group",
"usergroups": {
"uID": 2,
"gID": 1
}
}
]
}
]
But where are groups 2 and 3 for User1, and group 2 for User2?
It's almost like I need to get all the user ids in a specific group (through the Group service class) then do:
query.where = {id: {[Op.in]: userIdsArray}}