How to select data from left join table? - nestjs

I have tow entities: Users and Settings tables.
#Entity()
export class Users {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
settingID: number;
}
#Entity()
export class Settings {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
}
How to select data from Settings table ?
return this.usersRepository
.createQueryBuilder("users")
.select(['users.id', 'users.name', 'settings.name'])
.leftJoinAndSelect(Settings, 'settings', 'users.settingID = settings.id')
.getMany();
Now I don't get data from Settings table, only users table.

return this.usersRepository
.createQueryBuilder("users")
.select(['users.id', 'users.name'])
.leftJoinAndSelect('users.settings', 'settings', 'users.settingID = settings.id')
.addSelect(['settings.name', 'settings.prop1', 'settings.prop2'])
.getMany();
or
return this.usersRepository
.createQueryBuilder("users")
.leftJoinAndSelect('users.settings', 'settings', 'users.settingID = settings.id')
.select(['users.id', 'users.name', 'settings.name', 'settings.prop1', 'settings.prop2'])
.getMany();

Related

TypeORM create a record with the ForeignKey

I have two entities questionnaires and sections. A questionnaire has multiple sections so the relation is OneToMany.
When I try to create a new record for the questionnaire - it works just fine. I get an id and then I create a section record and reference the id there.
Response, when I try to create a section, is this
{
"name": "Section 1",
"questionnaire_id": 1,
"description": "Section 1 description",
"id": 1
}
As you see it returns questionnaire_id back but the problem is that that record in DB is questionnaire_id=null. If I change it manually and assign an id value to it then it works and I get my JSON document with sections in it.
If I fetch all records from the DB then I get this
[
{
"id": 1,
"name": "Section 1",
"description": "Section 1 description"
}
]
questionnaire_id is not present because it's null. 😕.
Do you know what I might be doing wrong? I have a feeling it has something to do with Entity relation but not sure what exactly.
#Entity({ name: 'questionnaires' })
export class Questionnaire {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'varchar', length: 128 })
name: string;
#Column({ type: 'enum', enum: STATUS, default: STATUS.ACTIVE })
status: STATUS;
#OneToMany(() => Section, (section) => section.questionnaire)
#JoinColumn({ name: 'questionnaire_id' })
sections: Section[];
}
#Entity({ name: 'sections' })
export class Section {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'varchar', length: 128 })
name: string;
#Column({ type: 'text', default: '' })
description: string;
#ManyToOne(() => Questionnaire, (questionnaire) => questionnaire.sections)
questionnaire: Questionnaire;
}
The goal is to get all the questionnaires with their' sections.
The way I'm trying to achieve is this
#Injectable()
export class QuestionnaireService {
constructor(#InjectRepository(Questionnaire) private readonly questionnaire: Repository<Questionnaire>) {}
create(createQuestionnaireDto: CreateQuestionnaireDto) {
return this.questionnaire.save(createQuestionnaireDto);
}
findAll() {
return this.questionnaire.find({
relations: ['sections'],
});
}
}
#Injectable()
export class SectionService {
constructor(#InjectRepository(Section) private readonly sectionRepository: Repository<Section>) {}
create(createSectionDto: CreateSectionDto) {
return this.sectionRepository.save(createSectionDto);
}
findAll() {
return this.sectionRepository.find();
}
}
export class CreateSectionDto {
#ApiProperty({
description: 'Section name',
example: 'Section 1',
})
#IsString()
#IsNotEmpty()
#MaxLength(128)
name: string;
#ApiProperty({
description: 'Relation to the questionnaire',
example: 1,
})
#IsNumber()
questionnaire_id: number;
#ApiProperty({
description: 'Section description',
example: 'Section 1 description',
default: '',
})
#IsString()
#IsOptional()
#MaxLength(512)
description?: string;
}
p.s.
I checked this one TypeORM insert row with foreign key
but did not work for me.
EDIT:
This is what query is executed when running insert command
query: START TRANSACTION
query: INSERT INTO "sections"("created_at", "updated_at", "name", "description", "questionnaire_id") VALUES (DEFAULT, DEFAULT, $1, $2, DEFAULT) RETURNING "created_at", "updated_at", "id", "description" -- PARAMETERS: ["Section 1","Section 1 description"]
query: COMMIT
I have "created_at", "updated_at" fields too but I did not include in the examples above.
As it's visible it includes questionnaire_id but for some reason the value is set to DEFAULT. Not passing the one I'm sending via http request.
SOLUTION
The problem was my approach of creating records. Since I've set up a DB relation and the Section's table is dependent on the Questionnaire's table - TypeORM does not allow me to create sections records with my FK value. The library handles it automatically if I created Questionnaire first and assigned Sections object to it.
With the configration above the line below works just fine
async create(createQuestionnaireDto: CreateQuestionnaireDto) {
const questionnaire = this.questionnaire.create(createQuestionnaireDto);
const sections = new Section();
sections.name = 'Default Section';
sections.summary = 'Default Section Summary';
sections.questionnaire = questionnaire;
await this.questionnaire.save(questionnaire);
await this.section.save(sections);
return questionnaire;
}
And the query it is running is this
query: START TRANSACTION
query: INSERT INTO "sections"("created_at", "updated_at", "name", "summary", "questionnaire_id") VALUES (DEFAULT, DEFAULT, $1, $2, $3) RETURNING "created_at", "updated_at", "id", "summary" -- PARAMETERS: ["Default Section","Default Section Summary",1]
query: COMMIT
As you can see there is no DEFAULT for questionnaire_id.
If I wanted my initial approach to make work I should not be using TypeORM relation decorators, which is a pretty bad idea.
As I have not used TypeORM very often so I am not totally sure about the answer but you can check this out.
So you probably doing wrong in defining your Entity relation. You are using #JoinColumn in Questionnaire Entity.
It should be defined in Sections Entity like below:
#Entity({ name: 'sections' })
export class Section {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'varchar', length: 128 })
name: string;
#Column({ type: 'text', default: '' })
description: string;
#ManyToOne(() => Questionnaire, (questionnaire) => questionnaire.sections)
#JoinColumn({ name: 'questionnaire_id' })
questionnaire: Questionnaire;
}
The answer is based on this document https://typeorm.io/relations#joincolumn-options. Hope it solves your issue.
EDIT : (not related might be deprecated but mentioned in official docs)
Are you sure loading relations this way works ?
findAll() {
return this.questionnaire.find({
relations: ['sections'],
});
}
As I can see in documents HERE, they are doing something else.
findAll() {
return this.questionnaire.find({
relations: {
sections: true,
},
});
}
EDIT 2:
So if you now add a Column into your section entity like this it will work.
#Entity({ name: 'sections' })
export class Section {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'varchar', length: 128 })
name: string;
#Column({ type: 'text', default: '' })
description: string;
#Column({ type: 'number' })
questionnaire_id: number;
#ManyToOne(() => Questionnaire, (questionnaire) => questionnaire.sections)
#JoinColumn({ name: 'questionnaire_id' })
questionnaire: Questionnaire;
}
Let me know if this works.

nestjsx/crud typeorm relations query

How do I have to write the query in order to get relations of the entity populated with?
Checked the below options, but none of them works.
localhost:3000/reservations?join=concert_id
localhost:3000/reservations?join=concert
If I change the concert_id key within the join object of #Crud() options to concert (as the name of the property in the Reservation.entity.ts file is just concert, not concert_id), then I get an error error: column reference "Reservation_id" is ambiguous.
Any help is much appreciated. Thanks in advance!
Reservation.entity.ts
#Entity('reservations')
export class Reservation {
#PrimaryGeneratedColumn()
id: number;
#Column({ name: 'booking_reference' })
bookingReference: string;
#ManyToOne(() => Concert, (concert) => concert.reservations)
#JoinColumn({ name: 'concert_id' })
concert: Concert;
}
Concert.entity.ts
#Entity('concerts')
export class Concert {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToMany(() => Reservation, (reservation) => reservation.concert)
reservations: Reservation[];
}
Reservation.controller.ts
#Crud({
model: {
type: Reservation
},
dto: {
create: CreateReservationDto
},
validation: { always: true },
query: {
join: {
concert_id: {
eager: true,
alias: "concert"
}
}
}
})
#ApiTags('Reservation')
#Controller('reservation')
export class ReservationController implements CrudController<Reservation> {
constructor(public service: ReservationService) {}

Nestjs typeorm many to many order by count

I have two entities image and tag with many to many relationship
#Entity()
export class Image {
#PrimaryGeneratedColumn()
id: number;
#Column('text', { nullable: true })
caption: string;
#Column('text', { nullable: false })
url: string;
#Column('text', { nullable: false })
thumbnailUrl: string;
#ManyToOne(() => User, {
eager: true,
})
uploader: User;
#JoinTable()
#ManyToMany(() => Tag, (tag: Tag) => tag.images, {
cascade: true,
eager: true,
})
tags?: Tag[];
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
and Tag entity
#Entity()
export class Tag {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany(() => Image, (image) => image.tags)
images: Image[];
}
Now I want to fetch popular tags. For that take 10 tags that have highest number of images created in last 7 days. For this i tried following in tag.repository.ts
getPopular() {
return (
this.createQueryBuilder('tag')
.addSelect((query) => {
return query
.select('COUNT(I.id)', 'count')
.from('image', 'I')
.where('I.created_at > :date', {
date: DateTime.now().minus({ weeks: 1 }).toISODate(),
});
}, 'imagecount')
.orderBy('imagecount', 'DESC')
.take(10)
.getMany()
);
But this will count all images that are created in last 7 days and will not consider if the image has that tag or not. So how should I load relation so that it count images with tags?
I would suggest you change your approach a little bit and try grouping after filtering by the last 7 days. Also, unless you have an explicit case where you will be adding images to a tag, I would recommend dropping the images property in your Tag entity since you already have a way to associate tags with an Image.
this.repository
.createQueryBuilder('image')
.where('image.created_at > :created_at', {
created_at: '2022-01-14 01:02:26', // make sure you set your own date here
})
.leftJoinAndSelect('image.tags', 'tag')
.groupBy('tag.id') // here is where we grup by the tag so we can count
.addGroupBy('tag.id')
.select('tag.id, count(tag.id)') // here is where we count :)
.orderBy('count(tag.id)', 'DESC')
.limit(10) // here is the limit
.execute();
These two sets of documentation could help you better in understanding how this query is built.
https://orkhan.gitbook.io/typeorm/docs/many-to-many-relations
https://github.com/typeorm/typeorm/blob/master/docs/select-query-builder.md#adding-group-by-expression

TypeORM - How to insert into table with foreign key without fetching relation first

I am creating a small app using NestJS and TypeORM and I am having trouble inserting into a table that has a composite foreign key.
Whenever the insert is executed, all columns get filled, except for the foreign keys, which stay as null.
Here are my entities:
Employee
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
#Entity({ name: 'employees' })
export class Employee {
#PrimaryGeneratedColumn('uuid')
id: string;
#PrimaryColumn()
version: number;
#Column({ name: 'employee_name', type: 'varchar' })
employeeName: string;
#Column({ name: 'employee_salary', type: 'numeric' })
employeeSalary: string;
}
Employee Payroll
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Employee } from '../../employee/entities/employee.entity';
#Entity({ name: 'employee_payrolls' })
export class EmployeePayroll {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ name: 'employee_payroll_name', nullable: true })
employeePayrollName: string;
#ManyToOne(() => Employee)
#JoinColumn([
{ name: 'employee_id', referencedColumnName: 'id' },
{ name: 'employee_version', referencedColumnName: 'version' },
])
employee: Employee;
}
Employee Payroll Service
And here is the code that is doing the insert:
export class EmployeePayrollDTO {
employeePayrollName: string;
employeeId: string;
employeeVersion: number;
}
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { EmployeePayrollDTO } from 'src/employee-payroll/dto/employee-payroll.dto';
import { EmployeePayroll } from 'src/employee-payroll/entities/employee-payroll.entity';
import { Repository } from 'typeorm';
#Injectable()
export class EmployeePayrollService {
constructor(
#InjectRepository(EmployeePayroll)
private readonly employeeRepository: Repository<EmployeePayroll>,
) {}
async addEmployeePayroll(employeePayroll: EmployeePayrollDTO) {
return await this.employeeRepository
.createQueryBuilder()
.insert()
.into(EmployeePayroll)
.values(employeePayroll)
.execute();
}
}
The Problem
The problem is that while the insert is successful, the values of the columns: employee_id and employee_version are null:
id
employee_payroll_name
employee_id
employee_version
53de51fd-6c9e-4b96-8906-edd1f6eea26c
Payroll 1
null
null
64b8a147-acee-4f43-9ea1-b64c2c036369
Payroll 2
null
null
Can you help me?
As per #Michael Levi's comment, the problem was that I wasn't setting the employee object correctly.
Here is what worked for me:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { EmployeePayrollDTO } from 'src/employee-payroll/dto/employee-payroll.dto';
import { EmployeePayroll } from 'src/employee-payroll/entities/employee-payroll.entity';
import { Repository } from 'typeorm';
#Injectable()
export class EmployeePayrollService {
constructor(
#InjectRepository(EmployeePayroll)
private readonly employeeRepository: Repository<EmployeePayroll>,
) {}
async addEmployeePayroll(employeePayroll: EmployeePayrollDTO) {
return await this.employeeRepository
.createQueryBuilder()
.insert()
.into(EmployeePayroll)
.values({
employeePayrollName: employeePayroll.employeePayrollName,
employee: {
id: employeePayroll.employeeId,
version: employeePayroll.employeeVersion,
},
})
.execute();
}
}
Please note that this only inserts records in the EmployeePayroll table, and if I try to use a new id or version inside the employee object, I get a foreign key violation error.

mongoose Document doesn't map a property correctly

I have a nodeJS Server with express project with a mongoDB.
I want to map this object, coming from an endpoint:
{
userId: '5ca14305e73fc8453843d3e1',
bookingId: '29b0c5e0-e504-43bc-b5a1-9326d7d41d45'
}
Its defined through a class like this:
export class CreateBookerDto {
#IsString() readonly userId: string;
#IsString() readonly bookingId: string;
}
This is my actual code doing the mapping:
export interface IBooker extends Document {
userId: string;
bookingId: string;
createdAt?: number;
}
constructor(
#InjectModel('Booker') private readonly bookerModel: Model<IBooker>
) { }
async createBooker(booker: CreateBookerDto) {
let createdBooker = new this.bookerModel(booker);
createdBooker.createdAt = moment.now();
return createdBooker.save();
}
This is my output from createdBooker before the save:
{
_id: 5ca146e4ba2c08380c435453,
bookingId: '29b0c5e0-e504-43bc-b5a1-9326d7d41d45'
}
Where and why does he drop the properties userId and createdAt?

Resources