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.
Related
I only found following in Sequelize documentation, but it's not possible to understand how to fetch associations properly.
Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' })
// This will create the table PersonChildren which stores the ids of the objects.
This is the implementation.
const sequelizePsqlConfig = new Sequelize(
"postgres://Kavinda Vindika#localhost:5432/hrm_users"
);
export const User = sequelizePsqlConfig.define(
"users",
{
userId: {
field: "user_id",
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
userName: {
field: "user_name",
type: DataTypes.STRING,
},
email: { type: DataTypes.STRING, key: "email" },
department: { type: DataTypes.STRING, key: "department" },
designation: { type: DataTypes.STRING, key: "designation" },
},
{
timestamps: false,
}
);
export const User_Associations = sequelizePsqlConfig.define(
"user_associations",
{
id: {
field: "id",
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
managerId: {
field: "manager_id",
type: DataTypes.INTEGER,
},
subordinateId: {
field: "subordinate_id",
type: DataTypes.INTEGER,
},
},
{
modelName: "user_associations",
timestamps: false,
tableName: "user_associations",
}
);
User.belongsToMany(User, {
as: "manager",
through: "user_associations",
foreignKey: "manager_id",
otherKey: "subordinate_id",
});
User.belongsToMany(User, {
as: "subordinate",
through: "user_associations",
foreignKey: "subordinate_id",
otherKey: "manager_id",
});
Then I tried to fetch the data with manager and subordinate details.
public async getAllUsers() {
return User.findAll({
include: ["manager", "subordinate"],
});
}
But it failed with following logs. Please assist me on this issue.
[1] Executing (default): SELECT "users"."user_id" AS "userId", "users"."user_name" AS "userName", "users"."email", "users"."department", "users"."designation", "manager"."user_id" AS "manager.userId", "manager"."user_name" AS "manager.userName", "manager"."email" AS "manager.email", "manager"."department" AS "manager.department", "manager"."designation" AS "manager.designation", "manager->user_associations"."id" AS "manager.user_associations.id", "manager->user_associations"."manager_id" AS "manager.user_associations.managerId", "manager->user_associations"."subordinate_id" AS "manager.user_associations.subordinateId", "manager->user_associations"."manager_id" AS "manager.user_associations.manager_id", "manager->user_associations"."subordinate_id" AS "manager.user_associations.subordinate_id", "subordinate"."user_id" AS "subordinate.userId", "subordinate"."user_name" AS "subordinate.userName", "subordinate"."email" AS "subordinate.email", "subordinate"."department" AS "subordinate.department", "subordinate"."designation" AS "subordinate.designation", "subordinate->user_associations"."id" AS "subordinate.user_associations.id", "subordinate->user_associations"."manager_id" AS "subordinate.user_associations.managerId", "subordinate->user_associations"."subordinate_id" AS "subordinate.user_associations.subordinateId", "subordinate->user_associations"."manager_id" AS "subordinate.user_associations.manager_id", "subordinate->user_associations"."subordinate_id" AS "subordinate.user_associations.subordinate_id" FROM "users" AS "users" LEFT OUTER JOIN ( "user_associations" AS "manager->user_associations" INNER JOIN "users" AS "manager" ON "manager"."user_id" = "manager->user_associations"."subordinate_id") ON "users"."user_id" = "manager->user_associations"."manager_id" LEFT OUTER JOIN ( "user_associations" AS "subordinate->user_associations" INNER JOIN "users" AS "subordinate" ON "subordinate"."user_id" = "subordinate->user_associations"."manager_id") ON "users"."user_id" = "subordinate->user_associations"."subordinate_id";
[1] TypeError: Class constructor model cannot be invoked without 'new'
I am creating a chat application and trying to create a relationship between User and User through friendship. So far I can create a relationship, but in the end only one user assigned a friend. I'm using Express, sequalize with postgress deployed to heroku. I don't know how to achieve it, any help is appreciated
migration:
`enter code here`await queryInterface.createTable('users', { id: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4, } }, }); await queryInterface.createTable('friendships', { id: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4, }, user: { type: DataTypes.UUID, allowNull: false, references: { model: 'users', key: 'id' }, }, friend: { type: DataTypes.UUID, allowNull: false, references: { model: 'users', key: 'id' }, }, status: { type: DataTypes.ENUM('PENDING', 'ACCEPTED', 'DENIED'), allowNull: false, defaultValue: 'PENDING', }, });
friendship model:
Friendship.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
user: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id',
},
},
friend: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id',
},
},
status: {
type: DataTypes.ENUM('PENDING', 'ACCEPTED', 'DENIED'),
allowNull: false,
defaultValue: 'PENDING',
},
},
{
sequelize,
underscored: true,
timestamps: false,
modelName: 'friendship',
}
);
associations:
User.belongsToMany(User, { as: 'friends', through: Friendship, foreignKey: 'user',
otherKey: 'friend' });
Friendship.belongsTo(User, { as: 'info', foreignKey: 'friend' });
controller:
export const addFriend = async (req: Request, res: Response) => {
try {
const { friendId } = req.body;
const userId = req.decodedToken?.id;
const friend = await User.findByPk(friendId, {
attributes: { exclude: ['passwordHash'] },
include: [{ model: User, as: 'friends' }],
});
if (friend)
await Friendship.create({
user: userId,
friend: friend.id,
status: 'PENDING',
});
return res.status(200).json({ status: 'success' });
} catch (e) {
res.status(500).json({ error: 'The server cannot create the user' });
console.log(e);
}
};
response:
"users": {
"count": 2,
"rows": [
{
"id": "e7c7ce39-953a-45e7-a892-a5f99554382e",
"email": "admin",
"username": "admin",
"name": null,
"surname": null,
"age": null,
"public": true,
"image": null,
"friends": [
{
"id": "13029ad9-7199-47d5-bd1c-7d939b26150e",
"email": "admin2",
"username": "admin",
"passwordHash": "$2b$10$GajlewYeiGvUOOV08YOzLuedV/8.KJNUeHB4WPKlUFxErj91ljfWq",
"name": null,
"surname": null,
"age": null,
"public": true,
"image": null,
"friendship": {
"id": "a78b9336-f5a6-4153-b3e1-e44dbe1cc7a6",
"user": "e7c7ce39-953a-45e7-a892-a5f99554382e",
"friend": "13029ad9-7199-47d5-bd1c-7d939b26150e",
"status": "PENDING"
}
}
]
},
{
"id": "13029ad9-7199-47d5-bd1c-7d939b26150e",
"email": "admin2",
"username": "admin",
"name": null,
"surname": null,
"age": null,
"public": true,
"image": null,
"friends": []
}
Edit:
I made something like this, it's not perfect, but it's good for me
User.belongsToMany(User, { as: 'friends', through: Friendship, foreignKey: 'user' });
User.belongsToMany(User, { as: 'friend', through: Friendship, foreignKey: 'friend' });
Please consider a many to many relationship.
An user can be a friend of many users.
Many users can be friends of one user.
You may found the how-to implement in the official doc: here.
Basically, use belongsToMany in both associations:
Implementation
The main way to do this in Sequelize is as follows:
const Movie = sequelize.define('Movie', { name: DataTypes.STRING });
const Actor = sequelize.define('Actor', { name: DataTypes.STRING });
Movie.belongsToMany(Actor, { through: 'ActorMovies' });
Actor.belongsToMany(Movie, { through: 'ActorMovies' });
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'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 }
]
});
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}}