CandidateEntity
@Entity({ name: 'users' })
export class CandidateEntity {
@PrimaryGeneratedColumn()
public id: number;
@OneToOne(() => CandidateEmployeeInfoEntity, employeeInfo => employeeInfo.candidate)
public employeeInfo: CandidateEmployeeInfoEntity;
}
EmployeeInfoEntity
@Entity({ name: 'candidates_employee_infos' })
export class CandidateEmployeeInfoEntity {
@PrimaryGeneratedColumn()
public id: number;
@Column({ type: 'bool', nullable: false })
public relocation: boolean;
@Column({ type: 'text', nullable: true })
public softSkills: string;
@OneToOne(() => CandidateEntity, candidate => candidate.employeeInfo)
public candidate: CandidateEntity;
@Column({ type: 'integer' })
public candidateId: number;
}
I create query to select first 25 rows from 104 rows in database
const {
perPage = 25,
page = 1,
} = params;
const skip = (perPage * page) - perPage;
let candidatesQuery = this.candidateRepository.createQueryBuilder('candidates');
candidatesQuery = candidatesQuery.leftJoinAndSelect(`candidates.employeeInfo`, 'employeeInfo'); // problem in this relation
candidatesQuery = candidatesQuery.skip(skip);
candidatesQuery = candidatesQuery.take(perPage);
const { entities, raw } = await candidatesQuery.getRawAndEntities();
const count = await candidatesQuery.getCount();
console.log(entities.length) // 104 rows
console.log(raw.length) // 104 rows
console.log(count) // 104 rows
Output sql query when typeorm return not correct results
first query
SELECT DISTINCT "distinctAlias"."candidates_id" as "ids_candidates_id" FROM (SELECT "candidates"."id" AS "candidates_id", "candidates"."uuid" AS "candidates_uuid", "candidates"."role" AS "candidates_role", "candidates"."first_name" AS "candidates_first_name", "candidates"."last_name" AS "candidates_last_name", "candidates"."email" AS "candidates_email", "candidates"."phone" AS "candidates_phone", "candidates"."phone_prefix" AS "candidates_phone_prefix", "candidates"."country_id" AS "candidates_country_id", "candidates"."city_id" AS "candidates_city_id", "candidates"."avatar" AS "candidates_avatar", "candidates"."confirmed_at" AS "candidates_confirmed_at", "candidates"."is_generated" AS "candidates_is_generated", "candidates"."created_at" AS "candidates_created_at", "candidates"."birthday" AS "candidates_birthday", "candidates"."type" AS "candidates_type", "employeeInfo"."id" AS "employeeInfo_id", "employeeInfo"."hourly_rate_from" AS "employeeInfo_hourly_rate_from", "employeeInfo"."hourly_rate_to" AS "employeeInfo_hourly_rate_to", "employeeInfo"."hourly_rate_currency" AS "employeeInfo_hourly_rate_currency", "employeeInfo"."salary_rate_from" AS "employeeInfo_salary_rate_from", "employeeInfo"."salary_rate_to" AS "employeeInfo_salary_rate_to", "employeeInfo"."salary_rate_currency" AS "employeeInfo_salary_rate_currency", "employeeInfo"."relocation" AS "employeeInfo_relocation", "employeeInfo"."soft_skills" AS "employeeInfo_soft_skills", "employeeInfo"."candidate_id" AS "employeeInfo_candidate_id" FROM "users" "candidates" LEFT JOIN "candidates_employee_infos" "employeeInfo" ON "employeeInfo"."candidate_id"="candidates"."id" WHERE "candidates"."type" IN ($1)) "distinctAlias" ORDER BY "candidates_id" ASC LIMIT 25
second query
SELECT "candidates"."id" AS "candidates_id", "candidates"."uuid" AS "candidates_uuid", "candidates"."role" AS "candidates_role", "candidates"."first_name" AS "candidates_first_name", "candidates"."last_name" AS "candidates_last_name", "candidates"."email" AS "candidates_email", "candidates"."phone" AS "candidates_phone", "candidates"."phone_prefix" AS "candidates_phone_prefix", "candidates"."country_id" AS "candidates_country_id", "candidates"."city_id" AS "candidates_city_id", "candidates"."avatar" AS "candidates_avatar", "candidates"."confirmed_at" AS "candidates_confirmed_at", "candidates"."is_generated" AS "candidates_is_generated", "candidates"."created_at" AS "candidates_created_at", "candidates"."birthday" AS "candidates_birthday", "candidates"."type" AS "candidates_type", "employeeInfo"."id" AS "employeeInfo_id", "employeeInfo"."hourly_rate_from" AS "employeeInfo_hourly_rate_from", "employeeInfo"."hourly_rate_to" AS "employeeInfo_hourly_rate_to", "employeeInfo"."hourly_rate_currency" AS "employeeInfo_hourly_rate_currency", "employeeInfo"."salary_rate_from" AS "employeeInfo_salary_rate_from", "employeeInfo"."salary_rate_to" AS "employeeInfo_salary_rate_to", "employeeInfo"."salary_rate_currency" AS "employeeInfo_salary_rate_currency", "employeeInfo"."relocation" AS "employeeInfo_relocation", "employeeInfo"."soft_skills" AS "employeeInfo_soft_skills", "employeeInfo"."candidate_id" AS "employeeInfo_candidate_id" FROM "users" "candidates" LEFT JOIN "candidates_employee_infos" "employeeInfo" ON "employeeInfo"."candidate_id"="candidates"."id" WHERE "candidates"."type" IN ($1)
third query
SELECT COUNT(DISTINCT("candidates"."id")) as "cnt" FROM "users" "candidates" LEFT JOIN "candidates_employee_infos" "employeeInfo" ON "employeeInfo"."candidate_id"="candidates"."id" WHERE "candidates"."type" IN ($1)
When I remove load relation employeeInfo.
This line
candidatesQuery = candidatesQuery.leftJoinAndSelect('candidates.employeeInfo', 'employeeInfo');
TypeORM return 25 rows
console.log(entities.length) // 25 rows
console.log(raw.length) // 25 rows
console.log(count) // 104 rows
Why ? And how to fix this problem ?
When you use Leftjoin typeorm doesn't add the LIMIT to its query.
From the creator of TypeORM:
take and skip functionality does not work with raw data since its internal ORM functionality https://github.com/typeorm/typeorm/issues/1768
So to fix it there are two options:
If you want typeORM to do it for you, you should use getMany, instead of raw
If you want raw results you should can use .offset and .limit instead of .skip, .take. Since .offset and .limit will change the SQL query.
Note: it is a good idea to order by something when you are implementing pagination
You can use take and skip with repository pattern:
const {
perPage = 25,
page = 1,
} = params;
const skip = (perPage * page) - perPage;
const candidates = await this.candidateRepository.find({
relations: ['employeeInfo'],
take: perPage,
skip,
});
also for pagination purpose I recommend to use findAndCount
const { perPage = 20, page = 1 } = params;
const skip = (perPage * page) - perPage;
const [data, total] = await this.repo.findAndCount({
take,
skip,
// ...other options
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With