Nestjs how to properly map 2 tables with a middle table - nestjs

I'm using Postgres as database have the next tables:
Students
id: Integer (PK)
name: Text
Subject
id: Integer (PK)
name: Text
student_assignation
student_id: Integer (PK)
subject_id: Integer (PK)
Those tables haven't an Auto-generated PK.
So, my entities are:
Student.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, PrimaryColumn } from 'typeorm';
import { student_assignation } from './student_assignation.entity';
import { Subject } from './subject.entity';
#Entity()
export class Student {
#Column('number')
#PrimaryColumn()
id: number;
#Column('text')
name: string;
#OneToMany(type => student_assignation, x => x.subject_id)
//student_assignations: student_assignation[];
}
Well, here is my question:
I'm trying to get all the subjects assigned to a user.
In SQL terms, I would define it like:
SELECT
u.id, u.name, r.id, r.name
FROM
student u INNER JOIN student_assignation ra
ON u.id = ra.student_id
INNER JOIN subject r
ON r.id = ra.subject_id
WHERE
u.id = 1
But at the moment to convert and use it in nestjs, I have this relation:
#OneToMany(type => student_assignation, x => x.subject_id)
#ManyToOne(type => subject, x => x.id)
But, is not retrieving any information.

You need many-to-many relation. Take a look on TypeOrm implementation here

You have to implement your entities in many-to-many relation:
#Entity()
export class Student {
#Column('number')
#PrimaryColumn()
id: number;
#Column('text')
name: string;
#ManyToMany(type => Subject)
#JoinTable({ name: 'student_assignation' })
subjects: Subject[];
}
#Entity()
export class Subject {
#PrimaryColumn()
id: number;
#Column()
name: string;
#ManyToMany(type => Student)
students: Student[];
}
To retrieve a user with all subjects:
const user = await User.findOne(USER_ID, { relations: ['subjects'] })
console.log(user.subjects);

Related

Cannot query across one-to-many for property in NestJS Postgresql

That's the updateEntity.ts
import { IsNotEmpty } from 'class-validator'
import { BaseEntity, Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
import { Company } from './company.entity'
#Entity('countries')
export class Country extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string
#IsNotEmpty()
#Column({ unique: true })
name: string
#ManyToOne(() => Company, (company) => company.locations, { nullable: true })
#JoinColumn({ name: 'company_id' })
countryId: Company[]
}
CompanyEntity.ts with the location field
#OneToMany(() => Country, (country) => country.countryId, { eager: true })
locations: Array<Country>
and here is the function where I want to update the properties
async update(id: number, updateCompanyDto: UpdateCompanyDto) {
const newLocations = updateCompanyDto.locations.map((location) => Country.create(location))
updateCompanyDto.locations = newLocations
const status = await Company.update(id, updateCompanyDto)
if (status.affected <= 0) {
throw new HttpException('This company does not exist', HttpStatus.NOT_FOUND)
}
return status
}
First time working with OneToMany and ManyToMany, and if I have the request body like this
"locations": [{"name":"Paris"}]
I'm getting an error "Cannot query across one-to-many for property locations"
I just want to be able to update the companies
It's okay because if you need to update,create or delete data from Location entity,you need to query using that entity not from joined entity

How to implement many-to-many query in typeorm

I'm working with typeORM with nestJS.
I have bi-directional many-to-many relationship between 2 tables: A and B (means a entity of A can be assign to many entities of B, and vice versa)
A.entity.ts:
import { Entity, Column, PrimaryGeneratedColumn, ManyToMany, JoinTable } from 'typeorm';
import { B } from './B.entity';
#Entity('A')
export class A {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany(() => B,
(b)=>(b.AObjects))
BObjects: B[];
}
B.entity.ts:
import { Entity, Column, PrimaryGeneratedColumn, ManyToMany, JoinTable } from 'typeorm';
import { A} from './A.entity';
#Entity('B')
export class B{
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany(() => A,
(a) => a.BObjects,
{eager:true})
#JoinTable({
name: 'AB',
inverseJoinColumn : { name: 'Aid', referencedColumnName: 'id'},
joinColumn: { name: 'Bid', referencedColumnName: 'id'},
})
AObjects: A[];
}
In the module service I want to implement a function, which receives a given id of B entity, and retreive all A's objects which refers to B's id
I want to write a typeORM query which implements the following sql query, for a given_B_id (which will be supplied as a parameter):
SELECT A.*
from A, AB
where AB.Bid = given_B_id and A.id = AB.Aid
Will appreciate your help
I finally find a workaround solution.
But still will appreciate your feedback and advices about the best way to implement many-to-many request with constraint with TypeORM.
My workaround based on queryRunner of DataSource
in the service constructor: add private member :
#Injectable()
export class AService {
constructor(
#InjectRepository(A)
private workerRepository: Repository<A>,
private dataSource: DataSource
){}
// .....
}
and in the "#GET" route handler using
async getByB(given_B_id: number): Promise<A[]> {
let AObjects :Promise<A[]>
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
try {
AObjects = await queryRunner.query(
` SELECT A.*
FROM A, AB
WHERE AB.Bid = ${given_B_id}
AND A.id = AB.Aid`
)
} catch (ex) {
throw ex;
} finally {
await queryRunner.release();
}
return(AObjects)
};
You can use find method as below
const res = this.BRepository.find({
where: { id: given_B_id },
relations: ['A']
});
Or use queryBuilder:
const res = this.BRepository
.createQueryBuilder('b')
.where('id = :b_id', { b_id: given_B_id })
.leftJoinAndSelect('B.A', 'A')
.getMany();
For more information check out the official TypeORM Docs.

How do I cause n + 1 problems with NestJs TypeOrm?

I was studying TypeOrm and I'm trying to create an N+1 problem, but it's not happening properly. Company and employee have a 1:N relationship.
Could you tell me why N + 1 is not causing any problems? I've tried setting up Lazy and setting up Eager, but I've been doing left join continuously so that n + 1 doesn't cause problems.
entity
#Entity('COMPANY')
export class Company extends TimeStamped {
#PrimaryGeneratedColumn('increment')
companyId: number;
#Column({ type: 'varchar' })
companyName: string;
#OneToMany(() => Employee, (employee) => employee.company, {
onDelete: 'CASCADE'
})
employee: Employee[];
}
#Entity('EMPLOYEE')
export class Employee extends TimeStamped {
#PrimaryGeneratedColumn('increment')
employeeId: number;
#Column({ type: 'varchar' })
employeeName: string;
#ManyToOne(() => Company, (company) => company.employee)
#JoinColumn([{ name: 'companyId', referencedColumnName: 'companyId' }])
company: Company;
}
crud
#Injectable()
export class CompanyService {
constructor(
#InjectRepository(Company)
private readonly companyRepository: Repository<Company>
) {}
getAllCompany() {
return this.companyRepository.find({ relations: ['employee'] });
}
getCompany(companyId: number) {
return this.companyRepository.findOne(companyId, {
relations: ['employee']
});
}
setCompany(setComanyDto: SetCompanyDto) {
return this.companyRepository.save(setComanyDto);
}
}
#Injectable()
export class EmployeeService {
constructor(
#InjectRepository(Employee)
private readonly employeeRepository: Repository<Employee>,
#InjectRepository(Company)
private readonly companyRepository: Repository<Company>
) {}
getAllEmployee() {
return this.employeeRepository.find({
relations: ['company']
});
}
getEmployee(employeeId: number) {
return this.employeeRepository.findOne(employeeId, {
relations: ['company']
});
}
async setEmployee(setEmployeeDto: SetEmployeeDto) {
const employee: Employee = new Employee();
employee.employeeName = setEmployeeDto.employeeName;
employee.company = await this.companyRepository.findOne(
setEmployeeDto.companyId
);
return this.employeeRepository.save(employee);
}
}
I believe you have a good idea about what N+1 problem is. You can check this question if you need to understand it more clearly.
If you use eager loading, you will not see the N+1 problem anyway since it joins the related entity and return both entities in one query.
If you specify relations as you've done below, again you will not see the N+1 problem since it creates a join query and returns all in 1 single query.
this.companyRepository.find({ relations: ['employee'] });
To create the N+1 problem,
Update your Company entity like below:
#Entity('COMPANY')
export class Company extends TimeStamped {
#PrimaryGeneratedColumn('increment')
companyId: number;
#Column({ type: 'varchar' })
companyName: string;
#OneToMany(() => Employee, (employee) => employee.company, {
onDelete: 'CASCADE',
lazy: true
})
employee: Promise<Employee[]>
}
In your CompanyService, create a new function to simulate the N+1 problem like below:
#Injectable()
export class CompanyService {
async createNPlus1Problem() {
// Query all companies (let's say you have N number of companies)
// SELECT * FROM "COMPANY";
const companies = this.companyRepository.find();
// The following `for` loop, loops through all N number of
// companies to get the employee data of each
for(company of companies) {
// Query employees of each company
// SELECT * FROM "EMPLOYEE" WHERE "companyId"=?;
const employees = await company.employee;
}
}
}
So in the above example, you have 1 query to get the company data. And N queries to get the employee data. Hence the N+1 problem.
Hope this clarifies your problem. Cheers 🍻 !!!
You can try to use this library https://github.com/Adrinalin4ik/Nestjs-Graphql-Tools it allows to overcome n+1 with the simple decorator. And it has minimum deps.
You can use leftJoinAndSelect method with query builder.
https://orkhan.gitbook.io/typeorm/docs/select-query-builder#joining-relations
const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.andWhere("photo.isRemoved = :isRemoved", { isRemoved: false })
.getOne()
SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'

How to implement history in typeorm?

I'm trying to follow after changes in entities with relations.
First of all, I have created 2 entities that have ManyToOne/OneToMany relation between them like:
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
uuid: string;
#Column()
name: string;
#OneToMany(() => Item , item => item.user)
items: Item[];
#UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(2)', select: false })
updateTime: Date;
}
#Entity()
export class Item extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
uuid: string;
#Column()
name: string;
#ManyToOne(() => User, user => user.items)
user: User;
#Column({ select: false })
userUuid: string;
#UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(2)', select: false })
updateTime: Date;
}
I'm using typeorm-revisions in order to follow after changes, so I add 2 entities to mantain a Revision History like in the docs:
#Entity()
export class UserHistory extends User implements HistoryEntityInterface {
makeActionAt: Date;
id: string = this.uuid;
#Column()
public originalID!: string;
#HistoryActionColumn()
public action!: HistoryActionType;
}
#Entity()
export class ItemHistory extends Item implements HistoryEntityInterface {
makeActionAt: Date;
id: string = this.uuid;
#Column()
public originalID!: string;
#HistoryActionColumn()
public action!: HistoryActionType;
}
and also subscribers:
#EventSubscriber()
class UserHistorySubscriber extends HistorySubscriber<User, UserHistory> {
public get entity() {
return User;
}
public get historyEntity() {
return UserHistory;
}
}
#EventSubscriber()
class ItemHistorySubscriber extends HistorySubscriber<Item, ItemHistory> {
public get entity() {
return Item;
}
public get historyEntity() {
return ItemHistory;
}
}
Now, when I run entityManager.save(user) or entityManager.save(item) while user/item can be a new entities or an updated one, it's work correctly - create/update the record inside User/Item accordingly and create a new record inside UserHistory/ItemHistory.
The problems started when I'm trying to get the right entity according to a specific updateTime with the right relation according to the same updateTime. I would like to be able to get the past version of the user with the most updated items according to the updateTime of the user. This is what I tried (not working properly):
const query = await this.createQueryBuilder('user')
.leftJoinAndSelect('user.items', 'item')
.where('user.uuid= :uuid', { uuid });
.where('user.updateTime= :updateTime', { updateTime });
// I use getMany() because in my real scenario the query is more complicated then this
return await query.getMany();
So, for ex. at some point the DB looks like this:
user:
uuid
name
updateTime
123
bob
2021-6-21
userHistory:
uuid
name
updateTime
originalID
action
364
bob
2021-6-21
123
UPDATED
789
Alvin
2021-6-17
123
CREATED
item:
uuid
name
userUuid
updateTime
990
phone
123
2021-6-21
145
cup
123
2021-6-20
itemHistory:
uuid
name
userUuid
updateTime
originalID
action
990
phone
123
2021-6-21
990
UPDATED
134
cup
123
2021-6-20
145
UPDATED
2s3
clock
123
2021-6-19
145
CREATED
3a9
phone
123
2021-6-16
990
CREATED
updateTime is actually a full timestamp but for convenience, I refer to them as dates.
and I want to do something like this:
const uuid = '123', updateTime = '2021-6-17'; // updateTime = '2021-6-18' will be the same
const query = await this.createQueryBuilder('user')
.leftJoinAndSelect('user.items', 'item')
.where('user.uuid= :uuid', { uuid });
.where('user.updateTime= :updateTime', { updateTime });
return await query.getMany();
So it will return:
User {
uuid: '123',
name: 'Alvin'
items: [
Item {
uuid: '990',
name: 'phone'
}
]
}
or with const uuid = '123', updateTime = '2021-6-21'; So it will return:
User {
uuid: '123',
name: 'bob'
items: [
Item {
uuid: '990',
name: 'phone'
},
Item {
uuid: '145',
name: 'cup'
}
]
}
Someone know what's the best way to implement it? thanks ahead!

TypeORM OneToMany filter in relations not effect to result

I have two tables:
#Entity('Reviews')
class Review {
...
#OneToMany((type) => MapCategory, map => map.review)
public categories: MapCategory[];
}
And:
#Entity('MapCategories')
export class MapCategory {
...
#ManyToOne(type => Review, (review) => review.categories)
public review: Review;
}
When I try the filter on 'categories' but the result doesn't filter 'categories' following the key that I already push.
const items = await this.reviewRepository.findAndCount({
relations: ['categories'],
where: {
categories: {
id: 1
}
}
});
We need to use queryBuilder for cases like this since find doesn't allow filtering relations:
const items = await reviewRepository.createQueryBuilder("review")
.leftJoinAndSelect("review.categories", "category")
.where("category.id = :id", { id })
.getManyAndCount()
I prefer to avoid query builder when possible.
There's a workaround for filtering based on relation fields for findOne()/find() methods that I've discovered recently. The problem with filtering related table fields only exists for ObjectLiteral-style where, while string conditions work perfectly.
Assume that we have two entities – User and Role, user belongs to one role, role has many users:
#Entity()
export class User {
name: string;
#ManyToOne(() => Role, role => role.users)
role: Role;
}
#Entity()
export class Role {
#OneToMany(() => User, user => user.role)
users: User[];
}
Now we can call findOne()/find() methods of EntityManager or repository:
roleRepository.find({
join: { alias: 'roles', innerJoin: { users: 'roles.users' } },
where: qb => {
qb.where({ // Filter Role fields
a: 1,
b: 2
}).andWhere('users.name = :userName', { userName: 'John Doe' }); // Filter related field
}
});
You can omit the join part if you've marked your relation as an eager one.

Resources