TypeOrm LeftJoin with 3 tables - mysql

i want to create Left Join between 3 tables in TypeORM (NesjJS)
Table 1: User (can have multiple establishments with multiple roles)
Table 2: Establishment (can be associated to multiple users having multiple roles)
Table 3: Role (listing associations between User/Establishment and role Level)
Example:
==> "user" table
ID
name
1
jiji
2
jaja
3
jojo
4
jeje
==> "establishment" table
ID
name
10
est 1
11
est 2
12
est 3
==> "role" table
user_id
establishment_id
level
1
10
admin
1
11
editor
2
10
editor
3
11
reader
3
12
admin
i try with this relations but do not working form me :( when i try to insert data to role entity
export class EstablishmentEntity{
#PrimaryGeneratedColumn()
id: number;
...
#ManyToMany(() => RoleEntity, role => role.establishments)
roles: Role[];
}
export class UserEntity{
#PrimaryGeneratedColumn()
id: number;
...
#ManyToMany(() => UserEntity, role => role.users)
roles: Role[];
}
export class RoleEntity{
#PrimaryGeneratedColumn()
id: number;
...
#ManyToMany(() => UserEntity, user => user.roles)
users: UserEntity[];
#ManyToMany(() => EstablishmentEntity, establishment => establishment.roles)
establishments: EstablishmentEntity[];
}
and JoinTable create for me 4 tables :(

From what I've understood - you would like to fetch users with all their establishments and level inside. Consider you have custom column level there you need to use OneToMany reference in UserEntity to roles and EstablishmentEntity and ManyToOne in RoleEntity instead of ManyToMany. ManyToMany would be useful if you would only have User and Establishments ids in 3rd table and wanted to fetch all Establishments by user at once.
// RoleEntity:
...
#ManyToOne(() => UserEntity, user => user.roles)
public user: UserEntity;
#ManyToOne(() => EstablishmentEntity, establishment => establishment.roles)
public establishment: EstablishmentEntity;
// UserEntity:
...
#OneToMany(() => RoleEntity, roles => roles.user)
public roles: RoleEntity[];
// EstablishmentEntity
...
#OneToMany(() => RoleEntity, roles => roles.establishment)
public roles: RoleEntity[];
After you will be able to fetch data using left joins:
const usersWithRoles = await connection
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.roles", "roles")
.leftJoinAndSelect("roles.establishment", "establishment")
.getMany();
It will return for you all users with their roles and in certain establishments.
If you would like to save new role, you can do it like this:
await connection
.getRepository(RoleEntity)
.createQueryBuilder()
.insert()
.values({user, establishment, level })
.execute()
Please refer to this part of documentation

Related

Typeorm many-to-many select rows with no relationship

I have two tables, that have many to many relationship.
#Entity()
export class DiscountPlan {
//...other columns
#ManyToMany(() => CustomerGroup, (group) => group.discountPlans)
#JoinTable()
groups: CustomerGroup[];
}
#Entity()
export class CustomerGroup {
//...other columns
#ManyToMany(() => DiscountPlan, (plan) => plan.groups)
discountPlans: DiscountPlan[];
}
now I need to get discount plans that either have no relations(no groups) or has relation with given groupId(it comes from customer entity).
I have to do it with queryBuilder.
thanks.

TypeORM find entities where entity.array contains id

I am using NestJS + typescript. This is my entity:
#Entity()
export class YearCourse {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany(() => User)
#JoinTable()
admins: User[];
}
And now given user I would like to find all yearCourse entities where given user is it's admin.
Prototype looks like this:
async getByUser(user: User): Promise<YearCourse[]> {
return this.yearCourseRepository.find({where: { admins.contains(user) }})
}
This obviously doesn't work. How to achieve this type of search?
To write more advanced SQL queries with TypeOrm, you can use the Query Builder.
Considering your admin id is stored as adminId (admin_id) in the User entity, it should look like this:
// query builder name ('year') is completely customisable
return this.yearCourseRepository.createQueryBuilder('year')
// load "admins" relation (user entity) and select results as "admins"
.leftJoinAndSelect('year.admins', 'admins')
// search where users have the "user.id" as "adminId"
.where('admins.adminId = :id', { id: user.id })
// get many results
.getMany();
I recommend you to write this directly in your repository file when using Query Builder for more cleanliness

Set Many-to-Many relationship decorator for two entities vs create a third entity?

In TypeORM, suppose I use MySQL database and there are two entities: Student and Course.
A student can learn many course, and a course can be enrolled by many student.
So there are two ways to solve n-n relationship:
Method #1: the official documents suggests me to add #ManyToMany and #JoinTable decorators into one of them. However, I find it hard to maintain the code when working with a large-scaled backend.
// src/entity/Student.ts
#Entity()
export class Student {
//...
}
// src/entity/Course.ts
#Entity()
export class Course {
//...
#ManyToMany(() => Student)
#JoinTable()
students: Student[];
}
Method #2: Create an entity (like StudentCourse) that link between them. TypeORM requires me to add a primary key into the middle table. This additional step will be time-wasting to add a primary key into all middle tables in a schema and this will increase size capacity.
// src/entity/Student.ts
#Entity()
export class Student {
//...
#OneToMany(type => StudentCourse, studentCourse => studentCourse.student)
studentCourses!: StudentCourse[];
}
// src/entity/Course.ts
#Entity()
export class Course {
//...
#OneToMany(type => StudentCourse, studentCourse => studentCourse.course)
studentCourses!: StudentCourse[];
}
// src/entity/StudentCourse.ts
#Entity()
export class StudentCourse {
#PrimaryGeneratedColumn({ type: "int", name: "id" })
id!: number;
// Some custom columns...
#ManyToOne(type => Student, student => student.studentCourses)
#JoinColumn([{ name: "student_id", referencedColumnName: "id" }])
student: Student;
#ManyToOne(type => Course, course => course.studentCourses)
#JoinColumn([{ name: "course_id", referencedColumnName: "id" }])
course: Course;
}
What I want to know is: Which method should I use to deal with Many-to-Many relationship? When and why? What are the pros and cons between them?
For the first method #ManyToMany: We use it when we don't have any other properties we weed between that relationship we want to build,
in your case this method suits you
Ps: #ManyToMany create an association table for us
2/ We use the second method is when we need to add more information for example if need to add a classroom, date then creating a new entity is a must.

How to create a relation for User, groups & groups metaData using TypeORM?

Creating the MySQL Table relationships, and confused to implement which Relationship should be implemented on User -> Groups -> GroupsMetaData.
User.id = OneToMany (Groups)
Groups = ManyToOne (Users)
Groups.id = ManyToMany (GroupsMetaData)
GroupsMetaData = ManyToOne(Groups)
Using TypeORM for implementing this approach.
Users.entity.ts
#OneToMany(type => Groups, groups => groups.uid)
groups:Groups[]
Groups.entity.ts
#ManyToOne(type => User, user => user.groups)
uid:User
Stucked to create the relations for Groups and GroupsMetaData.
Thanks
**It's not a professional way for an explanation, I tried for the best explanation
User entity
#OneToMany(type => Groups, groups => groups.uid)
groups:Groups[]
Group Entity
#ManyToOne(type => User, user => user.groups)
user: User
#ManyToOne(type => GroupMetadata, gmeta => gmeta.groups)
details: GroupMetadata
GroupMetadata Entity
#OneToMany(type => Groups, groups => groups.uid)
groups: Groups[]
With that config you'll have something like this:
A many to many relation between user and groupmetadata using group as junction table. I recommend you to change the name of some tables, group could be called "user_group" and group metadata can be renamed as group.
Let me know if this answers your question

Self-Referencing ManyToMany Relationship TypeORM

I have just started using TypeORM and I'm struggling getting the following relationship to work:
User->Friends, whereas a Friend is also a User Object.
My getters, getFriends & getFriendsInverse are working, however; I do now want to distinguish between the two. In other words; when I perform a mysql join I do not want to do a left join on friends and another one on inverseFriends.
The getter getFriends() needs to return all friends, regardless of which "side" the object I'm on.
Does that make sense?
This is my model definition:
getFriends() {
// This method should also return inverseFriends;
// I do not want to return the concat version; this should
// happen on the database/orm level
// So I dont want: this.friends.concat(this.inverseFriends)
return this.friends;
}
#ManyToMany(type => User, user => user.friendsInverse, {
cascadeInsert: false,
cascadeUpdate: false,
})
#JoinTable()
friends = [];
#ManyToMany(type => User, user => user.friends, {
cascadeInsert: true,
cascadeUpdate: true,
cascadeRemove: false,
})
friendsInverse = [];
I hope someone understands my question :D
Thanks
Matt
You can self-reference your relations. Here is an example of a simple directed graph (aka a node can have a parent and multiple children).
#Entity()
export class Service extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
#Index({ unique: true })
title: string;
#ManyToOne(type => Service, service => service.children)
parent: Service;
#OneToMany(type => Service, service => service.parent)
children: Service[];
}
An important note to keep in mind is that these relations are not auto loaded when reading an object from the DB with find* functions.
To actually load them, you have to use query builder at the moment and join them. (You can join multiple levels.) An example:
let allServices = await this.repository.createQueryBuilder('category')
.andWhere('category.price IS NULL')
.innerJoinAndSelect('category.children', 'product')
.leftJoinAndSelect('product.children', 'addon')
.getMany();
Please note how I used different names to reference them (category, product, and addon).
I believe I'm 3 years late, but better late than ever. The most upvoted answer does not answer the question, as it only works for tree-like and hierarchical structures, so if you follow that example, this would happen:
Fred
/ \
Albert Laura
/ \
John Foo
In this example, Foo can't be friends with Fred, because he can only have one parent. Friends is not a tree structure, it's like a net. The answer would be the following:
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
#Entity(UserModel.MODEL_NAME)
export class UserModel {
static MODEL_NAME = 'users';
#PrimaryGeneratedColumn()
id?: number;
#Column({ type: 'varchar', unique: true, length: 50 })
username: string;
#Column({ type: 'varchar', length: 50, unique: true })
email: string;
#ManyToMany(type => UserModel)
#JoinTable()
friends: UserModel[];
#Column({ type: 'varchar', length: 300 })
password: string;
}
This would create a table where relations between people would be saved. Now for the next important stuff. How do you query this and get a user's friends? It's not as easy as it seems, I've played hours with this and haven't been able to do it with TypeORM methods or even query builder. The answer is: Raw Query. This would return an array with the user's friends:
async findFriends(id: Id): Promise<UserModel[]> {
return await this.userORM.query(
` SELECT *
FROM users U
WHERE U.id <> $1
AND EXISTS(
SELECT 1
FROM users_friends_users F
WHERE (F."usersId_1" = $1 AND F."usersId_2" = U.id )
OR (F."usersId_2" = $1 AND F."usersId_1" = U.id )
); `,
[id],
);
}
(users_friends_users is the autogenerated name that typeORM gives to the table where the relations between users are saved)
2021 here, was searching for the same problem and find a way to solve it without custom raw SQL (providing same model as example for simplicity):
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn, JoinTable } from 'typeorm';
#Entity(UserModel.MODEL_NAME)
export class UserModel {
static MODEL_NAME = 'users';
#PrimaryGeneratedColumn()
id?: number;
#Column({ type: 'varchar', unique: true, length: 50 })
username: string;
#Column({ type: 'varchar', length: 50, unique: true })
email: string;
#ManyToMany(type => UserModel)
#JoinTable({ joinColumn: { name: 'users_id_1' } })
friends: UserModel[];
#Column({ type: 'varchar', length: 300 })
password: string;
}
The key moment here is to set joinColumn for JoinTable.
When you are defining ManyToMany relationship, TypeORM automatically creates n-n table users_friends_users with one column named user_id_1 and another user_id_2 (they are automatically incremented if foreign key is the same)
So it is enough to choose any column from this table as "primary join column" and it works
I'm working on a similar feature and facing the same problem.
Defined the same #ManyToMany relation as #Aleksandr Primak , but the relation isn't bi-directionnal.
Example:
Case 1 :
Auto-generated table users_friends_users contains value
[userId_1, userId_2] = 70, 19
When I'm logged with userId=70 and request currentUser, it returns the friends[19]
Case 2 :
Auto-generated table users_friends_users contains previously's value reversed
[userId_1, userId_2] = 19, 70
Still logged with userId=70 and request currentUser, it returns an empty list of friends[]
So I guess the only way is to use Raw Query as #Javi Marzán said