I have tree models User, Company and UserCompany.
I have associations
Company.belongsToMany(User, {
as: 'users',
through: {
model: UserCompany,
unique: false
},
onDelete: 'cascade'
});
User.belongsToMany(Company, {
as: 'companies',
through: {
model: UserCompany,
unique: false
},
onDelete: 'cascade'
});
I am trying to query companies buy runing this code
sql.Company.findAll({
where: query,
include: [{model:sql.User, as:'users', attributes: ['id', 'first_name', 'last_name', 'email']}],
order: sort,
limit: limit,
offset: offset
})
I have two elements of UserCompany with CompanyId=10 and UserId=50, but query returns only one of them.
It returns array
{id: 10,
...,
users: {
id: 50,
...,
UserCompany: {}
}
}
So UserCompany is not array, it is just one element. But I want to get them all.
How can I fix my association code?
Your associations are right. The UserCompany property isn't supposed to be showing all of each user's UserCompany relationships -- it's just showing the one that got the user included in that query's results.
If what you're looking for is a list of companies associated with each user in the returned array, you can add an include for that:
Company.findAll({
include: [{
model: User,
as: 'users',
attributes: ['id', 'first_name', 'last_name', 'email'],
// Add this
include: [{
model: Company,
as: 'companies',
attributes: ['id', 'name']
}],
}],
})
This gives you the following format in the returned array:
{
id: 1,
first_name: 'Nelson',
last_name: 'Bighetti',
email: 'bighead#hooli.com',
UserCompany: {...},
companies: [
{ id: 1, name: 'Hooli', UserCompany: {...} },
{ id: 2, name: 'Pied Piper', UserCompany: {...} }
]
}
Related
I am trying to count number of Followers and Followings of a user.
as followingCount and followerCount
User Model
User.init(
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
email: {
type: DataTypes.STRING,
},
password: {
type: DataTypes.STRING,
},
}
);
static associate(models) {
// Follow relationship
this.belongsToMany(models.User, {
through: 'UserFollow',
foreignKey: 'followerId',
as: 'following',
});
// Follow relationship
this.belongsToMany(models.User, {
through: 'UserFollow',
foreignKey: 'followeeId',
as: 'follower',
});
}
Where UserFollow is a Joint Table with columns followeeId and followerId.
My current approach for finding number of followings is something like this :
const user = await User.findOne({
where: {
id,
},
attributes: [
'id',
'userName',
'email',
[sequelize.fn('COUNT', sequelize.col('following->UserFollow.followeeId')), 'followingCount'],
],
include: [
{
model: User,
as: 'following',
attributes: ['id', 'userName', 'email'],
through: {
attributes: [],
},
},
],
group: ['User.id', 'following.id'],
});
return user;
And Output getting is like this:
Here I am getting followingCount as 1... but it should be 3.
"data": {
"id": "1af4b9ea-7c58-486f-a37a-e46461487b06",
"userName": "xyz",
"email": "xyz#gmail.com",
"followingCount": "1", <------ I want this to be 3
"following": [
{
"id": "484202b0-a6d9-416d-a8e2-6681deffa3d1",
"userName": "uqwheuo",
"email": "uqwheuo#gmail.com"
},
{
"id": "56c8d9b0-f5c6-4b2e-b32c-be6363294614",
"userName": "aiwhroanc",
"email": "aiwhroanc#gmail.com"
},
{
"id": "9a3e4074-c7a0-414e-8df4-cf448fbaf5fe",
"userName": "iehaocja",
"email": "iehaocja#gmail.com"
}
]
}
I am not able to count in Joint Table..
The reason that you are getting followingCount: 1 is that you group by following.id (followeeId). It only counts unique followeeId which is always 1.
Although, if you take out following.id from group, the SQL doesn't work any more. It will crash with "a column must appear in GROUP BY clause...". This is a common issue in Postgres and this link (https://stackoverflow.com/a/19602031/2956135) explains the topic well in detail.
To solve your question, instead of using group, you can use COUNT OVER (PARTITION BY).
const user = await User.findOne({
where: {
id,
},
attributes: [
'id',
'userName',
'email',
[Sequelize.literal('COUNT("following->UserFollow"."followeeId") OVER (PARTITION BY "User"."id")'), 'followingCount']
],
include: [
{
model: User,
as: 'following',
attributes: ['id', 'userName', 'email'],
through: {
attributes: [],
}
},
],
});
======================================================
Update:
The original query only fetch "following" relationship. In order to fetch followers of this user, you first need to add "follower" association.
Then, since 2 associations is added, we need to add 1 more partition by column to count exactly the followers or followees.
const followeeIdCol = '"following->UserFollow"."followeeId"';
const followerIdCol = '"follower->UserFollow"."followerId"';
const user = await User.findOne({
where: {
id,
},
attributes: [
'id',
'userName',
'email',
// Note that the COUNT column and partition by column is reversed.
[Sequelize.literal(`COUNT(${followeeIdCol}) OVER (PARTITION BY "Users"."id", ${followerIdCol})`), 'followingCount'],
[Sequelize.literal(`COUNT(${followerIdCol}) OVER (PARTITION BY "Users"."id", ${followeeIdCol})`), 'followerCount'],
],
include: [
{
model: User,
as: 'following',
attributes: ['id', 'userName', 'email'],
through: {
attributes: [],
}
},
{
model: User,
as: 'follower', // Add follower association
attributes: ['id', 'userName', 'email'],
through: {
attributes: [],
}
},
],
});
This is currently what my entities look like:
Category Entity
#Entity('category')
export class Category extends BaseEntity {
#PrimaryGeneratedColumn("uuid")
id: string;
#Column({ type: 'text', unique: true })
name: string;
#Column({ type: "text", unique: true })
#Index()
slug: string;
#ManyToMany(() => Listing, (listing) => listing.categories, { cascade: true, onDelete: 'CASCADE' })
listings?: Listing[];
}
Listing Entity
#Entity('listing')
export class Listing extends BaseEntity {
#PrimaryGeneratedColumn("uuid")
id: string;
#ManyToMany(() => Category, (category) => category.listings)
#JoinTable()
categories: Category[];
}
Query 1 (what I'm currently using)
And this is currently what my query looks like:
const listings = await connection.getRepository()
.createQueryBuilder('listing')
.distinct(true)
.leftJoinAndSelect('listing.categories', 'category', 'category.slug IN (:...slugs)', {slugs: [ 'mens-shirts', 'clearance' ]})
.getMany()
Query 1 Result
[] // an empty list of Listings (Type: Listing[])
Query 2 (checking to see if the innerJoinAndSelect was working properly)
const listings = await connection.getRepository()
.createQueryBuilder('listing')
.distinct(true)
.innerJoinAndSelect('listing.categories', 'category')
.getMany();
Query 2 Result
[
Listing {
id: 'c24ea98d-da53-4f14-8706-a3597f3ee4d1',
categories: [ [Category], [Category] ]
},
Listing {
id: 'e8b3e680-85b6-4701-9ad7-bf65de348e76',
categories: [ [Category], [Category] ]
},
Listing {
id: '1bb04ea0-8435-44d6-856f-8eb53f24e941',
categories: [ [Category], [Category] ]
},
Listing {
id: '0735142d-fd38-4fad-b5a7-0356373dd0a3',
categories: [ [Category], [Category] ]
},
]
The innerJoinAndSelect method is working and giving me the results back, and I know why I'm getting an empty array when using the first query. It's because I'm trying to find the field slug on the Array of Categories, instead of each Category in the Array.
Question:
How would I search for the slug names [ 'mens-shirts', 'clearance' ], in the array of Categories (Type: Category[]), using TypeORM's QueryBuilder? Or How could I check to see if each Category in the Categories field, has a slug, that is in [ 'mens-shirts', 'clearance' ]. Is it possible?
You need to use = ANY() when searching if an element is inside an array. So change 'category.slug IN (:...slugs)' to 'category.slug = ANY(:slugs)' (notice how you don't need to use "spread" inside ANY, just reference the array directly)
In my application, I am using sequelize ORM. There are several entities: A Tool can have Tags and Categories.
Now I want to search for all Tools, that have a specific Tag, but I want to include all relating Tags of that tool (not just the specific one). If I now place a where statement into the include, only specified Tags are included into the result set (see [2]). I tried to limit the Tags in the outer where statement (see [1]), but this does not help either.
Example
Tool A has Tags t1, t2 and t3. Now I want to search all Tools that have the Tag t3, but the result set shall contain all three tags.
Expected result:
Tool A
\
- Tag t1
- Tag t2
- Tag t3
db.Tool.scope('published').findAll({
where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
include: [
{
model: db.User,
attributes: ['id', 'username']
},
{
model: db.Tag,
attributes: ['name'],
through: { attributes: [] },
// [2] Would limit the result specified tag
// where: {
// name: {
// [Op.and]: filter.tag
// }
// }
},
{
model: db.Category,
attributes: ['id', 'name', 'views'],
through: { attributes: ['relevance'] },
where: {
id: {
[Op.and]: filter.category
}
}
}
],
where: {
title: {
[Op.like]: `%${filter.term}%`,
}
},
attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})
I know I could perform this by performing a select via the Tag in the first place (db.Tag.findAll() instead of db.Tool.findAll(); I've already done this elsewhere in my project), but at the same time I also want to be able to filter by another entity (Category) the same way. So the Tool.findAll() should be the starting point.
Any help appreciated!
First off, you have two where clauses in your top-level query:
where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
// ...
where: {
title: {
[Op.like]: `%${filter.term}%`,
}
},
I think your best approach is going to be with a literal subquery in the WHERE clause. Basically we want to find the ids of all of the tools that have the right tag and that contain the filter.term.
The subquery part for the WHERE looks something like...
SELECT ToolId FROM ToolTags WHERE TagId='t2';
Inspired by the subquery solution from this post Sequelize - subquery in where clause
// assuming your join table is named 'ToolTags' in the database--we need the real table name not the model name
const tempSQL = sequelize.dialect.QueryGenerator.selectQuery('ToolTags',{
attributes: ['ToolId'],
where: {
TagId: filter.tag
}})
.slice(0,-1); // to remove the ';' from the end of the SQL
db.Tool.scope('published').findAll({
where: {
title: {
[Op.like]: `%${filter.term}%`,
},
id: {
[Op.In]: sequelize.literal(`(${tempSQL})`)
}
},
include: [
{
model: db.User,
attributes: ['id', 'username']
},
{
model: db.Tag,
attributes: ['name'],
through: { attributes: [] },
},
// {
// model: db.Category,
// attributes: ['id', 'name', 'views'],
// through: { attributes: ['relevance'] },
// where: {
// id: {
// [Op.and]: filter.category
// }
// }
// }
],
attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})
I commented out your category join for now. I think you should try to isolate the solution for the tags before adding more onto the query.
Let us say there are two tables namely User and User Role.
The relationship between user and user role is one to many.
Sequelize model for the user is as following -
const user = sequelize.define(
'user', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
userName: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'username'
},
password: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'password'
}
}, {
tableName: 'user'
}
);
Sequelize model for user role is as follwing -
const userRole = sequelize.define(
'userRole', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
userId: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'user_id'
},
password: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'password'
}
}, {
tableName: 'userRole'
}
);
Sequelize association is defined as follows -
user.hasMany(models.userRole, { foreignKey: 'user_id', as: 'roles' });
userRole.belongsTo(models.user, { foreignKey: 'user_id', as: 'user' });
I want to generate the following query using
Sequelize -
SELECT *
FROM USER
INNER JOIN (SELECT user_role.user_id,
role
FROM user_role
INNER JOIN USER tu
ON tu.id = user_role.user_id
GROUP BY user_id
ORDER BY role) AS roles
ON USER.id = roles.user_id;
I am developing an API which will be consumed by the front end grid for showing user info. There is search functionality on role attribute of user role table. If any of role of a specific user is matched then I expect a user record with all the roles which are associated with that user.
To get all roles that are associated with the user even if one of them matches with a query you need to define another association so that you can include role table twice in sequelize statement.
User.hasMany(models.UserRole, { foreignKey: 'user_id', as: 'roles2' });
Sequelize statement -
const userInfo = await User.findAndCountAll({
include: [
{
model: UserRole,
attributes: ['id', 'role'],
as: 'roles',
where: { [Op.or]: [{ role: { [Op.like]: '%MANAGER%' } },
required: true
},
{
model: UserRole,
attributes: ['id', 'role'],
as: 'roles2',
required: true
}
],
where: whereStatement,
});
With the help of first include (join), you can filter user records based on user role and with the help of the second include(join), you can get all the roles of a user whose one of the roles is matched.
You have to use include (regarding the doc : https://sequelize.org/master/manual/models-usage.html#-code-findandcountall--code----search-for-multiple-elements-in-the-database--returns-both-data-and-total-count)
Exemple :
Models.User.findAll({
where :{
id: userId
},
group: ['roles.user_id'],
order: [['roles.role', 'ASC']] //or DESC, as you want
include: {
model: Models.UserRole,
as: 'roles',
attributes: ['user_id', 'role'],
required: true
},
})
Hope it helps you
A table contains 2 user id cols, userOneId and userTwoId. My user id could be in either col. I'd like to get all rows where my userId is in either col, and also include the associated object of the other user.
Here's the latest attempt. I query where my id is in userOneId or userTwoId, make an alias for the other user id, otherUserId, with a case/when. Then use alias otherUserId in an include, to return the other user associated object.
where: {
[Op.or]: [{
userOneId: userId,
}, {
userTwoId: userId,
}]
},
attributes: [
'id',
[sequelize.literal("CASE WHEN \"userOneId\" = " + userId + " THEN \"userTwoId\" ELSE \"userOneId\" END"), 'otherUserId']
],
include: {
where: {
id: '$otherUserId$'
},
model: sequelize.models.User,
as: 'otherUser',
attributes: [ 'id', 'name', ...
Model associations are:
MyModel.belongsTo(models.User, {
as: 'userOne',
foreignKey: 'userOneId'
})
MyModel.belongsTo(models.User, {
as: 'userTwo',
foreignKey: 'userTwoId'
})
User.hasMany(models.MyModel, {
as: 'userOne',
foreignKey: {
name: 'userOneId',
allowNull: false
}
})
User.hasMany(models.MyModel, {
as: 'userTwo',
foreignKey: {
name: 'userTwoId',
allowNull: false
}
})
However, it errors with
User is associated to MyModel multiple times. To identify the correct association, you must use the 'as' keyword to specify the alias of the association you want to include.
Any pointers on if or how to get this to work?