Nodejs

 

1) ex1

 

const users = await userRepository.createQueryBuilder("user")
    .leftJoinAndSelect("user.profile", "profile")
    .where("user.isActive = :isActive", { isActive: true })
    .orderBy("user.firstName", "ASC")
    .skip(10)
    .take(5)
    .getMany();
  1. createQueryBuilder("user")

    • userRepository에서 QueryBuilder를 생성하고 user 테이블을 기준으로 쿼리를 시작합니다.

  2. leftJoinAndSelect("user.profile", "profile")

    • user 테이블과 profile 테이블을 LEFT JOIN으로 연결합니다.

    • user.profile은 User 엔터티와 Profile 엔터티 간의 관계를 나타냅니다.

    • profile 별칭을 사용하여 조회할 수 있습니다.

  3. where("user.isActive = :isActive", { isActive: true })

    • user.isActive가 true인 사용자만 조회합니다.

    • :isActive는 바인딩 변수로, SQL 인젝션을 방지할 수 있습니다.

  4. orderBy("user.firstName", "ASC")

    • firstName을 기준으로 오름차순(ASC) 정렬합니다.

  5. skip(10)

    • 처음 10개의 결과를 건너뛰고 이후 결과를 조회합니다. (페이징 처리 시 사용)

  6. take(5)

    • 5개의 결과만 가져옵니다. (즉, 11번째부터 15번째까지의 데이터만 조회)

  7. getMany()

    • 조회된 여러 개의 엔터티를 배열 형태로 반환합니다.

실행되는 SQL 예시

위 코드가 실행되면 내부적으로 다음과 같은 SQL이 생성됩니다:

SELECT user.*, profile.*
FROM user
LEFT JOIN profile ON user.profileId = profile.id
WHERE user.isActive = true
ORDER BY user.firstName ASC
LIMIT 5 OFFSET 10;

활용 예시

  • 페이징 처리 (예: skip(0).take(10) → 첫 페이지, skip(10).take(10) → 두 번째 페이지)

  • 특정 조건을 가진 데이터 필터링

  • 관계형 데이터를 한 번에 조회 (leftJoinAndSelect 사용)

이렇게 QueryBuilder를 활용하면 복잡한 SQL 쿼리를 효율적으로 작성하고 관리할 수 있습니다.

 

 

  async findAll(title?: string): Promise<[Movie[], number]> {
 
    if (!title) {
      return [
        await this.movieRepository.find({
          relations: [ 'director', 'genres'],
        }),
        await this.movieRepository.count(),
      ];
    }

    return [
      await this.movieRepository.find({ where: { title: Like(`%${title}%`) } }),
      await this.movieRepository.count({
        where: { title: Like(`%${title}%`) },
      }),
    ];
  }

==========>쿼리빌더로 변경시 

  async findAll(title?: string): Promise<[Movie[], number]> {
      
    const qb= this.movieRepository.createQueryBuilder("moive")
     .leftJoinAndSelect("moive.director", "director")
     .leftJoinAndSelect("moive.genres", "genres");
    
     if(title){
      qb.where("moive.title LIKE :title", { title: `%${title}%` });
     }    
     qb.orderBy("moive.id", "DESC");
     
    return await qb.getManyAndCount();

 

 

 

 

2) QueryBuilder 실행타입 :  1.SELECT

 

const movie = await dataSource 
    .createQueryBuilder()
    .select("movie")
    .from(Movie, "movie")
    .leftJoinAndSelect("movie.detail", "detail")
    .leftJoinAndSelect("movie.director", "director")
    .leftJoinAndSelect("movie.genres", "genres")
    .where("movie.id = :id", { id: 1 })
    .getOne();
  1. createQueryBuilder()

    • dataSource에서 QueryBuilder를 생성합니다.

  2. select("movie")

    • movie 테이블에서 데이터를 조회하도록 지정합니다.

  3. from(Movie, "movie")

    • Movie 엔터티를 기준으로 movie 별칭을 사용하여 쿼리를 구성합니다.

  4. leftJoinAndSelect("movie.detail", "detail")

    • movie 테이블과 detail 테이블을 LEFT JOIN으로 연결하고 데이터를 함께 가져옵니다.

  5. leftJoinAndSelect("movie.director", "director")

    • movie 테이블과 director 테이블을 LEFT JOIN으로 연결하고 데이터를 함께 가져옵니다.

  6. leftJoinAndSelect("movie.genres", "genres")

    • movie 테이블과 genres 테이블을 LEFT JOIN으로 연결하고 데이터를 함께 가져옵니다.

  7. where("movie.id = :id", { id: 1 })

    • movie.id가 1인 특정 영화를 조회합니다.

  8. getOne()

    • 하나의 결과만 반환합니다. (데이터가 없으면 null 반환)

실행되는 SQL 예시 2

위 코드가 실행되면 내부적으로 다음과 같은 SQL이 생성됩니다:

SELECT movie.*, detail.*, director.*, genres.*
FROM movie
LEFT JOIN detail ON movie.detailId = detail.id
LEFT JOIN director ON movie.directorId = director.id
LEFT JOIN movie_genres ON movie.id = movie_genres.movieId
LEFT JOIN genres ON movie_genres.genreId = genres.id
WHERE movie.id = 1;

활용 예시

  • 특정 영화와 관련된 상세 정보, 감독 정보, 장르 정보를 한 번에 조회 가능

  • getOne()을 사용하여 단일 결과 조회 (영화 ID가 존재하지 않으면 null 반환)

  • 관계형 데이터를 한 번의 쿼리로 가져와 API 응답 속도를 향상

이렇게 QueryBuilder를 활용하면 복잡한 SQL 쿼리를 효율적으로 작성하고 관리할 수 있습니다.

 

 

 

3) QueryBuilder 실행타입 :   2.데이터 삽입 (INSERT)

 

데이터 삽입 (INSERT)

설명

이 코드에서는 createQueryBuilder()를 사용하여 Movie 테이블에 새로운 데이터를 삽입합니다.

await dataSource
    .createQueryBuilder()
    .insert()
    .into(Movie)
    .values([
        {
            title: "New Movie",
            genre: "Action",
            director: director,
            genres: genres
        }
    ])
    .execute();

주요 기능

  1. createQueryBuilder(): 새로운 QueryBuilder 인스턴스를 생성합니다.

  2. insert(): INSERT SQL 문을 실행할 것을 선언합니다.

  3. into(Movie): 삽입할 테이블을 Movie로 지정합니다.

  4. values([...]): 삽입할 데이터를 객체 형태로 전달합니다.

    • title: 영화 제목

    • genre: 장르

    • director: 감독 정보 (객체 또는 ID)

    • genres: 여러 장르를 포함하는 배열

  5. execute(): 쿼리를 실행합니다.

실행되는 SQL

INSERT INTO movie (title, genre, directorId) VALUES ('New Movie', 'Action', directorId);

추가 설명

  • director와 genres는 기존에 데이터베이스에 존재하는 값이어야 하며, directorId와 genres 관계 테이블을 적절히 처리해야 합니다.

  • 여러 개의 데이터를 한 번에 삽입할 수도 있습니다.

 

 

 

 

4) QueryBuilder 실행타입 :  3.데이터 업데이트 (UPDATE)

데이터 업데이트 (UPDATE)

설명

이 코드에서는 createQueryBuilder()를 사용하여 Movie 테이블의 데이터를 업데이트합니다.

await dataSource 
    .createQueryBuilder() 
    .update(Movie) 
    .set({ title: "Updated Title", genre: "Drama" }) 
    .where("id = :id", { id: 1 }) 
    .execute();

주요 기능

  1. createQueryBuilder(): 새로운 QueryBuilder 인스턴스를 생성합니다.

  2. update(Movie): 업데이트할 테이블을 지정합니다.

  3. set({...}): 변경할 컬럼 값을 설정합니다.

    • title: "Updated Title"로 변경

    • genre: "Drama"로 변경

  4. where("id = :id", { id: 1 }): id가 1인 행만 업데이트하도록 조건을 지정합니다.

  5. execute(): 쿼리를 실행합니다.

실행되는 SQL

UPDATE movie SET title = 'Updated Title', genre = 'Drama' WHERE id = 1;

추가 설명

  • where("id = :id", { id: 1 })에서 id = id로 잘못 작성하면 올바르게 동작하지 않습니다.

  • id = :id로 바꾼 후, 두 번째 매개변수 { id: 1 }을 전달해야 합니다.

  • 여러 개의 조건을 추가하려면 andWhere()를 사용할 수 있습니다.

 

 

 

5) QueryBuilder 실행타입 :  4.데이터 삭제 (DELETE)

데이터 삭제 (DELETE)

설명

이 코드에서는 createQueryBuilder()를 사용하여 Movie 테이블의 데이터를 삭제합니다.

await dataSource 
    .createQueryBuilder() 
    .delete() 
    .from(Movie) 
    .where("id = :id", { id: 1 }) 
    .execute();

주요 기능

  1. createQueryBuilder(): 새로운 QueryBuilder 인스턴스를 생성합니다.

  2. delete(): DELETE SQL 문을 실행할 것을 선언합니다.

  3. from(Movie): 삭제할 테이블을 지정합니다.

  4. where("id = :id", { id: 1 }): id가 1인 행만 삭제하도록 조건을 지정합니다.

  5. execute(): 쿼리를 실행합니다.

실행되는 SQL

DELETE FROM movie WHERE id = 1;

추가 설명

  • where("id = id", { id: 1 })는 잘못된 구문으로, 올바르게 동작하지 않습니다.

  • 올바른 방식은 where("id = :id", { id: 1 })처럼 :id 바인딩을 사용하는 것입니다.

  • 여러 개의 조건을 추가하려면 andWhere()를 사용할 수 있습니다.

  • 특정 조건 없이 삭제하면 테이블의 모든 데이터가 삭제될 수 있으므로 주의해야 합니다.

 

 

6) QueryBuilder 실행타입 :  5.관계 데이터 로드 (RELATION LOAD)

관계 데이터 로드 (RELATION LOAD)

설명

이 코드에서는 createQueryBuilder()를 사용하여 Movie 엔터티와 관계를 가진 genres 데이터를 불러옵니다.

const genres = await dataSource 
    .createQueryBuilder() 
    .relation(Movie, "genres") 
    .of(1) // Movie id 
    .loadMany();

주요 기능

  1. createQueryBuilder(): 새로운 QueryBuilder 인스턴스를 생성합니다.

  2. relation(Movie, "genres"): Movie 엔터티의 genres 관계 데이터를 불러오도록 지정합니다.

  3. of(1): ID가 1인 Movie 엔터티의 genres 관계 데이터를 조회합니다.

  4. loadMany(): 여러 개의 관계 데이터를 가져오는 메서드입니다.

실행되는 SQL

SELECT genres.* FROM genres 
INNER JOIN movie_genres ON genres.id = movie_genres.genreId 
WHERE movie_genres.movieId = 1;

추가 설명

  • Movie 엔터티와 genres 관계가 @ManyToMany 또는 @OneToMany 관계일 경우 사용됩니다.

  • loadMany()는 여러 개의 관계 데이터를 가져올 때 사용하며, 하나만 가져오려면 loadOne()을 사용할 수 있습니다.

  • of() 메서드는 특정 엔터티를 지정하는 데 사용되며, 여러 개의 ID를 전달할 수도 있습니다.

 

 

 

 

 

7) 데이터 조회 (SELECT, getOne(), getMany())

데이터 조회 (SELECT, getOne(), getMany())

설명

이 코드는 createQueryBuilder()를 사용하여 User 엔터티의 데이터를 조회하는 방식입니다.

const user = await connection.getRepository(User)
    .createQueryBuilder("user")
    .select(["user.id", "user.firstName", "user.lastName"])
    .getOne();
const users = await connection.getRepository(User)
    .createQueryBuilder("user")
    .select(["user.id", "user.firstName", "user.lastName"])
    .getMany();

주요 기능

  1. getRepository(User): User 엔터티의 저장소를 가져옵니다.

  2. createQueryBuilder("user"): User 엔터티를 user라는 별칭으로 설정하여 쿼리를 생성합니다.

  3. select([...]): 조회할 컬럼을 지정합니다.

    • user.id

    • user.firstName

    • user.lastName

  4. getOne(): 조건에 맞는 단일 데이터를 가져옵니다.

  5. getMany(): 조건에 맞는 여러 개의 데이터를 가져옵니다.

실행되는 SQL

  • getOne()을 사용할 경우:

SELECT user.id, user.firstName, user.lastName FROM user LIMIT 1;
  • getMany()을 사용할 경우:

SELECT user.id, user.firstName, user.lastName FROM user;

추가 설명

  • getOne()은 결과가 없을 경우 null을 반환하며, 둘 이상의 결과가 있으면 오류가 발생합니다.

  • getMany()는 여러 개의 데이터를 배열 형태로 반환합니다.

  • select([...])를 사용하지 않으면 기본적으로 모든 컬럼을 조회합니다.

  • where(), orderBy(), limit(), offset() 등과 함께 사용할 수 있습니다.

 

 

 

 

 

8) where() 조건 적용 예시

where() 조건 적용 예시

1) 하나의 필터링 조건 적용

const users = await connection.getRepository(User)
    .createQueryBuilder("user")
    .where("user.isActive = :isActive", { isActive: true })
    .getMany();
  • where("user.isActive = :isActive", { isActive: true })를 사용하여 isActive 값이 true인 사용자만 조회합니다.

  •  

2) 다수의 필터링 조건 적용

const users = await connection.getRepository(User)
    .createQueryBuilder("user")
    .where("user.firstName = :firstName", { firstName: "John" })
    .andWhere("user.lastName = :lastName", { lastName: "Doe" })
    .getMany();
  • where("user.firstName = :firstName", { firstName: "John" })로 firstName이 "John"인 사용자를 필터링합니다.

  • andWhere("user.lastName = :lastName", { lastName: "Doe" })를 추가하여 lastName이 "Doe"인 사용자도 함께 필터링합니다.

실행되는 SQL

  • 하나의 조건 적용 시:

SELECT * FROM user WHERE user.isActive = true;
  • 다수의 조건 적용 시:

SELECT * FROM user WHERE user.firstName = 'John' AND user.lastName = 'Doe';

추가 설명

  • where()는 SQL의 WHERE 절과 동일한 역할을 합니다.

  • andWhere()는 여러 조건을 AND 연산자로 연결하여 필터링할 때 사용됩니다.

  • orWhere()를 사용하면 OR 조건을 추가할 수도 있습니다.

  • where() 내부에서 변수 바인딩을 할 때는 :변수명 형태로 사용하며, 두 번째 인자로 객체를 전달하여 값을 설정합니다.

 

 

 

 

 

9) orderBy() 조건 적용 예시

 

기본적인 orderBy() 사용법

TypeORM의 Query Builder를 사용할 경우, orderBy() 메서드를 사용하여 특정 컬럼을 기준으로 정렬할 수 있습니다. 기본적인 문법은 다음과 같습니다:

const users = await connection.getRepository(User)
  .createQueryBuilder("user")
  .orderBy("user.lastName", "ASC") // lastName을 기준으로 오름차순 정렬
  .getMany();

위 코드에서는 lastName을 기준으로 오름차순(ASC) 정렬합니다.

orderBy()와 addOrderBy()의 차이점

  • orderBy(): 처음 지정한 정렬 기준

  • addOrderBy(): 추가적인 정렬 조건을 지정할 때 사용

예를 들어, lastName을 오름차순으로 정렬한 후, 같은 성을 가진 사용자들에 대해 firstName을 내림차순으로 정렬하려면 다음과 같이 작성합니다.

const users = await connection.getRepository(User)
  .createQueryBuilder("user")
  .orderBy("user.lastName", "ASC") // 성을 기준으로 오름차순 정렬
  .addOrderBy("user.firstName", "DESC") // 이름을 기준으로 내림차순 정렬
  .getMany();

NestJS 환경에서 TypeORM을 활용한 정렬 예제

NestJS의 서비스 레이어에서 TypeORM을 활용하여 데이터를 정렬하여 반환하는 예제를 살펴보겠습니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async getSortedUsers(): Promise<User[]> {
    return this.userRepository
      .createQueryBuilder("user")
      .orderBy("user.lastName", "ASC")
      .addOrderBy("user.firstName", "DESC")
      .getMany();
  }
}

이 서비스는 lastName을 오름차순, firstName을 내림차순으로 정렬한 사용자 목록을 반환합니다.

결론

  • orderBy()는 정렬 기준을 설정하는 메서드입니다.

  • addOrderBy()를 사용하면 추가 정렬 기준을 지정할 수 있습니다.

  • NestJS에서 TypeORM을 활용하여 서비스 레이어에서 정렬된 데이터를 반환할 수 있습니다.

이제 NestJS 프로젝트에서 Query Builder를 활용하여 데이터를 원하는 순서로 정렬할 수 있습니다!

 

 

 

 

 

 

 

10) skip() 및 take() 사용법

기본적인 skip() 및 take() 사용법

skip()과 take()는 데이터 조회 시 페이지네이션을 적용할 때 사용됩니다.

  • skip(n): n개의 데이터를 건너뛰고 조회합니다.

  • take(n): n개의 데이터를 가져옵니다.

예제 코드:

const users = await connection.getRepository(User)
  .createQueryBuilder("user")
  .skip(10) // 처음 10개의 데이터를 건너뛰고
  .take(5)  // 이후 5개의 데이터를 가져옴
  .getMany();

위 코드는 사용자 목록에서 처음 10개를 건너뛴 후, 5개의 데이터를 반환합니다.

NestJS 환경에서 Pagination 적용 예제

서비스 레이어에서 페이징을 적용하는 방법을 예제로 살펴보겠습니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async getUsersWithPagination(page: number, pageSize: number): Promise<User[]> {
    const skip = (page - 1) * pageSize;
    return this.userRepository
      .createQueryBuilder("user")
      .skip(skip)
      .take(pageSize)
      .getMany();
  }
}

설명

  • page: 현재 페이지 번호

  • pageSize: 한 페이지에 포함될 데이터 개수

  • skip: (page - 1) * pageSize를 사용하여 건너뛸 개수를 계산

예를 들어, page=2이고 pageSize=5라면 skip(5)가 적용되어 6번째 데이터부터 5개를 가져오게 됩니다.

결론

  • skip(n): n개의 데이터를 건너뛰고 조회

  • take(n): n개의 데이터를 가져옴

  • NestJS 서비스에서 페이지네이션을 적용하여 효율적으로 데이터를 조회할 수 있음

이제 NestJS 프로젝트에서 Query Builder를 활용하여 효과적인 페이지네이션을 구현할 수 있습니다!

 

 

 

 

 

11) NestJS Query Builder에서 Join() 사용법

 

NestJS에서 TypeORM의 Query Builder를 활용하여 JOIN을 수행하는 방법을 설명합니다. TypeORM에서는 INNER JOIN과 LEFT JOIN을 사용할 수 있으며, joinAndSelect() 메서드를 활용하여 관련 엔터티의 데이터를 함께 조회할 수 있습니다.

1. INNER JOIN 사용법

INNER JOIN은 두 테이블 간에 공통된 데이터가 있을 때만 결과를 반환합니다.

const users = await connection.getRepository(User)
  .createQueryBuilder("user")
  .innerJoinAndSelect("user.profile", "profile")
  .getMany();

설명

  • innerJoinAndSelect("user.profile", "profile"): User 엔터티와 Profile 엔터티를 INNER JOIN하여 profile 정보를 함께 가져옴

  • getMany(): 여러 개의 데이터를 조회

이 쿼리는 User 엔터티와 연관된 Profile 엔터티가 존재하는 경우에만 데이터를 반환합니다.

2. LEFT JOIN 사용법

LEFT JOIN은 왼쪽(User) 테이블의 모든 데이터를 포함하며, 오른쪽(Photo) 테이블에 해당하는 데이터가 없을 경우 NULL을 반환합니다.

const users = await connection.getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.photos", "photo")
  .getMany();

설명

  • leftJoinAndSelect("user.photos", "photo"): User 엔터티와 Photo 엔터티를 LEFT JOIN하여 photo 정보를 함께 가져옴

  • getMany(): 여러 개의 데이터를 조회

이 쿼리는 User 엔터티의 모든 데이터를 가져오며, Photo 엔터티와의 관계가 없는 경우에도 User 데이터는 반환됩니다.

NestJS에서 Join을 활용한 예제

서비스 레이어에서 INNER JOIN과 LEFT JOIN을 활용하여 데이터를 조회하는 예제를 살펴보겠습니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async getUsersWithProfiles(): Promise<User[]> {
    return this.userRepository
      .createQueryBuilder("user")
      .innerJoinAndSelect("user.profile", "profile")
      .getMany();
  }

  async getUsersWithPhotos(): Promise<User[]> {
    return this.userRepository
      .createQueryBuilder("user")
      .leftJoinAndSelect("user.photos", "photo")
      .getMany();
  }
}

결론

  • innerJoinAndSelect(): 두 테이블 간에 일치하는 데이터가 있는 경우만 반환

  • leftJoinAndSelect(): 왼쪽 테이블의 모든 데이터를 반환하며, 오른쪽 테이블의 데이터가 없으면 NULL 포함

  • NestJS의 서비스에서 Query Builder를 활용하여 관계형 데이터를 효과적으로 조회할 수 있음

이제 NestJS 프로젝트에서 JOIN을 활용하여 데이터 관계를 효율적으로 조회할 수 있습니다!

 

 

 

 

 

12) NestJS Query Builder에서 Aggregation() 사용법

 

NestJS에서 TypeORM의 Query Builder를 활용하여 집계(Aggregation) 함수를 사용하는 방법을 설명합니다. COUNT(), SUM(), AVG(), MIN(), MAX() 등을 활용하여 데이터의 통계를 구할 수 있습니다.

1. COUNT() 사용법

COUNT() 함수는 특정 컬럼의 개수를 계산할 때 사용됩니다.

const userCount = await connection.getRepository(User)
  .createQueryBuilder("user")
  .select("COUNT(user.id)", "count")
  .getRawOne();

설명

  • select("COUNT(user.id)", "count"): user.id의 개수를 세어 count라는 별칭으로 반환

  • getRawOne(): 단일 결과를 객체로 반환 (예: { count: 10 })

2. SUM() 사용법

SUM() 함수는 특정 컬럼의 값을 합산할 때 사용됩니다.

const totalSalary = await connection.getRepository(User)
  .createQueryBuilder("user")
  .select("SUM(user.salary)", "totalSalary")
  .getRawOne();

설명

  • SUM(user.salary): 모든 사용자의 급여를 합산하여 totalSalary라는 별칭으로 반환

  • getRawOne(): 결과를 단일 객체로 반환 (예: { totalSalary: 50000 })

3. AVG() 사용법

AVG() 함수는 특정 컬럼의 평균값을 계산할 때 사용됩니다.

const averageAge = await connection.getRepository(User)
  .createQueryBuilder("user")
  .select("AVG(user.age)", "averageAge")
  .getRawOne();

설명

  • AVG(user.age): 모든 사용자의 나이 평균을 계산하여 averageAge라는 별칭으로 반환

4. MIN() 및 MAX() 사용법

MIN()과 MAX() 함수는 특정 컬럼의 최소값과 최대값을 구할 때 사용됩니다.

const minMaxSalary = await connection.getRepository(User)
  .createQueryBuilder("user")
  .select("MIN(user.salary)", "minSalary")
  .addSelect("MAX(user.salary)", "maxSalary")
  .getRawOne();

설명

  • MIN(user.salary): 최저 급여

  • MAX(user.salary): 최고 급여

  • addSelect(): 여러 개의 집계 함수를 한 번의 쿼리에서 사용할 때 추가

결과 예시:

{
  "minSalary": 30000,
  "maxSalary": 90000
}

NestJS 서비스에서 Aggregation 활용 예제

서비스 레이어에서 집계 함수를 활용하는 예제입니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async getUserCount(): Promise<number> {
    const result = await this.userRepository
      .createQueryBuilder("user")
      .select("COUNT(user.id)", "count")
      .getRawOne();
    return result.count;
  }

  async getTotalSalary(): Promise<number> {
    const result = await this.userRepository
      .createQueryBuilder("user")
      .select("SUM(user.salary)", "totalSalary")
      .getRawOne();
    return result.totalSalary;
  }
}

결론

  • COUNT(): 레코드 개수를 계산

  • SUM(): 특정 컬럼 값의 총합을 계산

  • AVG(): 특정 컬럼 값의 평균을 계산

  • MIN() / MAX(): 최소값과 최대값을 계산

  • NestJS 서비스에서 Query Builder를 활용하여 집계 데이터를 조회할 수 있음

이제 NestJS 프로젝트에서 Query Builder를 활용하여 집계 데이터를 효율적으로 조회할 수 있습니다!

 

 

 

 

 

 

 

13) NestJS Query Builder에서 SubQuery() 사용법

 

NestJS에서 TypeORM의 Query Builder를 활용하여 SUBQUERY(서브쿼리)를 사용하는 방법을 설명합니다. subQuery() 메서드를 활용하면 메인 쿼리 내에서 서브쿼리를 실행하여 특정 조건을 만족하는 데이터를 필터링할 수 있습니다.

1. 서브쿼리 기본 사용법

아래 코드는 isActive가 true인 사용자만 필터링하는 서브쿼리를 활용한 예제입니다.

const users = await connection.getRepository(User)
  .createQueryBuilder("user")
  .where(qb => {
    const subQuery = qb.subQuery()
      .select("subUser.id")
      .from(User, "subUser")
      .where("subUser.isActive = :isActive", { isActive: true })
      .getQuery();
    return `user.id IN (${subQuery})`;
  })
  .setParameter("isActive", true)
  .getMany();

설명

  • subQuery()를 사용하여 subUser.id를 선택하는 서브쿼리를 생성

  • from(User, "subUser")로 User 테이블을 지정

  • where("subUser.isActive = :isActive", { isActive: true })를 통해 활성화된 사용자만 선택

  • getQuery()를 사용하여 서브쿼리를 문자열로 변환

  • user.id IN (서브쿼리)를 사용하여 메인 쿼리에서 필터링

  • setParameter("isActive", true)를 통해 안전하게 값을 바인딩

2. 서브쿼리를 활용한 다른 예제

(1) 특정 그룹에 속한 사용자 찾기

const usersInGroup = await connection.getRepository(User)
  .createQueryBuilder("user")
  .where(qb => {
    const subQuery = qb.subQuery()
      .select("membership.userId")
      .from("Membership", "membership")
      .where("membership.groupId = :groupId", { groupId: 1 })
      .getQuery();
    return `user.id IN (${subQuery})`;
  })
  .setParameter("groupId", 1)
  .getMany();

(2) 특정 날짜 이후에 가입한 사용자 수 구하기

const recentUserCount = await connection.getRepository(User)
  .createQueryBuilder("user")
  .select("COUNT(user.id)", "count")
  .where(qb => {
    const subQuery = qb.subQuery()
      .select("subUser.id")
      .from(User, "subUser")
      .where("subUser.createdAt > :date", { date: "2024-01-01" })
      .getQuery();
    return `user.id IN (${subQuery})`;
  })
  .setParameter("date", "2024-01-01")
  .getRawOne();

3. NestJS 서비스에서 SubQuery 활용 예제

아래 코드는 isActive가 true인 사용자만 필터링하는 서브쿼리를 활용한 서비스 레이어 코드입니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async getActiveUsers(): Promise<User[]> {
    return this.userRepository
      .createQueryBuilder("user")
      .where(qb => {
        const subQuery = qb.subQuery()
          .select("subUser.id")
          .from(User, "subUser")
          .where("subUser.isActive = :isActive", { isActive: true })
          .getQuery();
        return `user.id IN (${subQuery})`;
      })
      .setParameter("isActive", true)
      .getMany();
  }
}

결론

  • subQuery()를 사용하면 메인 쿼리 내에서 서브쿼리를 실행할 수 있음

  • 서브쿼리는 필터링 및 특정 조건을 만족하는 데이터를 찾는 데 유용함

  • setParameter()를 활용하여 안전한 바인딩이 가능함

  • NestJS 서비스 레이어에서 Query Builder를 활용하여 복잡한 조건의 데이터를 효율적으로 조회할 수 있음

이제 NestJS 프로젝트에서 Query Builder의 subQuery()를 활용하여 더욱 정교한 데이터 조회가 가능합니다!

 

 

 

 

 

 ※Transaction 적용하기

 

기본 구조


  constructor(

    private readonly dataSource: DataSource,
  ) {}


 async create(createMovieDto: CreateMovieDto) {
    const qr=this.dataSource.createQueryRunner();
    await qr.connect();
    await qr.startTransaction();  
    try {
     
      
     await qr.commitTransaction();
    } catch (error) {

      await qr.rollbackTransaction();
      throw error;
    }finally{
    
      await qr.release();
    }


}

 

==========>적용

  async create(createMovieDto: CreateMovieDto) {
    const qr=this.dataSource.createQueryRunner();
    await qr.connect();
    await qr.startTransaction();  
    try {
      
      const dirctor = await qr.manager.findOne(Director,{
        where: { id: createMovieDto.directorId },
      });

      if (!dirctor) {
        throw new NotFoundException(`Director with ID ${createMovieDto.directorId} not found` );
      }

      const genres = await qr.manager.find(Director,{
        where: {
          id: In(createMovieDto.genreIds)
        }
      });

      if (genres.length!==createMovieDto.genreIds.length) {
        throw new NotFoundException(`존재하지 않는 장르가 있습니다. 존재하는 ids => ${genres.map((genre) => genre.id).join(', ')}`,);
      }

      const  movieTitleCheck  =await qr.manager.findOne(Movie,{
        where: { title: createMovieDto.title },
      });
      if (movieTitleCheck) {
        throw new ConflictException(`동일한 제목의 영화가 존재합니다. title => ${movieTitleCheck.title}`);
      }
      

      await this.movieDetailRepository.createQueryBuilder()
        .insert()
        .into(MovieDetail)
        .values({
          detail: createMovieDto.detail,              
        })
        .execute();

    const movie = await this.movieRepository.save({
      title: createMovieDto.title,
      genres,
      detail: {
        detail: createMovieDto.detail,
      },
      director: dirctor,
     });



     await qr.commitTransaction();
     return movie;
  
    } catch (error) {

      await qr.rollbackTransaction();
      throw error;
    }finally{
    
      await qr.release();
    }

}

 

 

 

 

 

about author

PHRASE

Level 60  라이트

군자는 스스로 재능이 없음을 근심하며, 남이 알아주지 않음을 근심하지 않는다. -공자

댓글 ( 0)

댓글 남기기

작성

Nodejs 목록    more