본문 바로가기
Research/Database

TypeORM_QueryBuilder_Select

by RIEM 2023. 2. 24.
728x90

TypeORM의 QueryBuilder에 대해 알아보겠습니다. 이 게시글은 TypeORM 공식문서를 반번역한 게시글이므로 상세한 참고를 위해서는 공식문서를 참고 부탁드립니다.
https://typeorm.io/select-query-builder#joining-relations

QueryBuilder란?

QueryBuilder는 SQL 쿼리를 사용하여 DB를 제어할 수 있는 기능을 말합니다. SQL문이 편한 사람들이 쓰기에 유용합니다.

  async test() {
    const result = await this.raffleRepository
      .createQueryBuilder('raffle')
      .where('raffle.raffleId = :id', { id: 100 })
      .getOne()
    return result;
  }
{
    "createdAt": "2023-02-22T08:36:57.121Z",
    "updatedAt": "2023-02-22T08:36:57.121Z",
    "deletedAt": null,
    "raffleId": 100,
    "dateStart": "2022-01-12",
    "dateEnd": "2022-04-04",
    "isClosed": true,
    "closedPrice": 223025,
    "usersId": 81948,
    "productId": 245
}

QueryBuilder를 사용할 때

QueryBuilder 사용 시, WHERE 절에는 unique parameter을 줘야 한다고 합니다.

const result = await dataSource
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.linkedSheep', 'linkedSheep')
    .leftJoinAndSelect('user.linkedCow', 'linkedCow')
    .where('user.linkedSheep = :id', { id: sheepId })
    .andWhere('user.linkedCow = :id', { id: cowId });

:id가 중복이므로 안됩니다.

const result = await dataSource
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.linkedSheep', 'linkedSheep')
    .leftJoinAndSelect('user.linkedCow', 'linkedCow')
    .where('user.linkedSheep = :sheepId', { sheepId })
    .andWhere('user.linkedCow = :cowId', { cowId });

id들을 unique한 값으로 수정하면 됩니다. 어떤 이름도 상관없지만 중복만 안되면 됩니다. queryBuilder 내에서는 겹치면 안되나보군요.

QueryBuilder 어떻게 만들고 사용하는가

QueryBuilder을 만드는 방법

총 3가지 있습니다.

1. DataSource 사용해서 만들기

const user = await dataSource
    .createQueryBuilder()
    .select("user")
    .from(User, "user")
    .where("user.id = :id", { id: 1 })
    .getOne()

2. Entity Manager 사용해서 만들기

const user = await dataSource.manager
    .createQueryBuilder(User, "user")
    .where("user.id = :id", { id: 1 })
    .getOne()

3. Repository 사용해서 만들기

const user = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .where("user.id = :id", { id: 1 })
    .getOne()

QueryBuilder의 유형

1. SelectQueryBuilder

Select 문 쿼리를 생성하고 실행할 때 사용합니다

const user = await dataSource
    .createQueryBuilder()
    .select("user")
    .from(User, "user")
    .where("user.id = :id", { id: 1 })
    .getOne()

2. InsertQueryBuilder

await dataSource
    .createQueryBuilder()
    .insert()
    .into(User)
    .values([
        { firstName: "Timber", lastName: "Saw" },
        { firstName: "Phantom", lastName: "Lancer" },
    ])
    .execute()

3. UpdateQueryBuilder

await dataSource
    .createQueryBuilder()
    .update(User)
    .set({ firstName: "Timber", lastName: "Saw" })
    .where("id = :id", { id: 1 })
    .execute()

4. DeleteQueryBuilder

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

5. RelationQueryBuilder

관계 지어서 수행하는 쿼리를 만들고 실행할 때 사용합니다

await dataSource
    .createQueryBuilder()
    .relation(User,"photos")
    .of(id)
    .loadMany();

QueryBuilder로 값 얻기

getOne, getMany

하나의 데이터만 얻으려면 getOne 메소드를 사용합니다. 반대로 복수로 얻고싶다면 getMany를 사용하면 됩니다.

  async test() {
    const result = await this.raffleRepository
      .createQueryBuilder('raffle')
      .where('raffle.raffleId = :id OR raffle.productId = :productId', { id: 100, productId: 200 })
      .getMany()
    return result;
  }

raffleId가 100이거나 productId가 200인 데이터를 모두 가져와줘라는 명령어입니다.

[
    {
        "createdAt": "2023-02-22T08:36:57.121Z",
        "updatedAt": "2023-02-22T08:36:57.121Z",
        "deletedAt": null,
        "raffleId": 100,
        "dateStart": "2022-01-12",
        "dateEnd": "2022-04-04",
        "isClosed": true,
        "closedPrice": 223025,
        "usersId": 81948,
        "productId": 245
    },
    {
        "createdAt": "2023-02-22T08:37:29.285Z",
        "updatedAt": "2023-02-22T08:37:29.285Z",
        "deletedAt": null,
        "raffleId": 5094,
        "dateStart": "2022-07-20",
        "dateEnd": "2022-07-26",
        "isClosed": true,
        "closedPrice": 1207910,
        "usersId": 37658,
        "productId": 200
    }
]

결과도 2개가 반환되었습니다.

getOneOrFail

const result = await this.raffleRepository
      .createQueryBuilder('raffle')
      .where('raffle.productId = :productId', { productId: 200000 })
      .getOneOrFail()

하나를 찾고 없으면 EntityNotFoundError에러를 던져주는 getOneOrFail 메소드도 있습니다. 다소 극단적인 친구네요.

Select QueryBuilder로 얻을 수 있는 결과의 유형

Select 쿼리빌더를 사용해서 얻을 수 있는 결과의 유형은 2가지 입니다.

  1. entities
  2. raw results
    대부분은 DB로부터 빨간색 컬러를 가진 상품을 모두 가져와 달라는 진짜 엔티티들을 선택하는 경우가 대부분입니다. 하지만 빨간색 컬러의 데이터가 총 몇 개인지 합산을 할 필요도 있습니다. 이 데이터는 엔티티가 아니고 raw data입니다.
const result = await this.raffleRepository
    .createQueryBuilder('raffle')
    .where('raffle.isClosed = :boolean', { boolean: true })
    .getRawOne();
{
    "raffle_createdAt": "2023-02-22T08:37:18.885Z",
    "raffle_updatedAt": "2023-02-22T08:37:18.885Z",
    "raffle_deletedAt": null,
    "raffle_raffleId": 4003,
    "raffle_dateStart": "2022-04-24T15:00:00.000Z",
    "raffle_dateEnd": "2022-06-26T15:00:00.000Z",
    "raffle_isClosed": true,
    "raffle_closedPrice": 1629504,
    "raffle_usersId": 23868,
    "raffle_productId": 3233
}
const photosSums = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .select("user.id")
    .addSelect("SUM(user.photosCount)", "sum")
    .groupBy("user.id")
    .getRawMany()

// result will be like this: [{ id: 1, sum: 25 }, { id: 2, sum: 13 }

이런식으로도 가능하다고 합니다. 추후 래플과 관련된 세부 리스트 데이터가 생길 경우 이를 활용해주어야 겠습니다.

alias 사용하기

생략

parameter로 data escape하기

파라미터 값 따로 명시하기

const result = await this.raffleRepository
      .createQueryBuilder('raffle')
      .where('raffle.isClosed = :boolean', { boolean: true })
      .getRawOne();

이렇게 말고

 const result = await this.raffleRepository
      .createQueryBuilder('raffle')
      .where('raffle.isClosed = :boolean')
      .setParameter('boolean', true)
      .getRawOne();

setParameter() 메소드로 따로 명시하면 깔끔하고 안전합니다. paramater은 중복으로 사용하면 override 한다고 합니다. unique한 파라미터만 사용합니다.

파라미터 값으로 [] 리스트

 const result = await this.raffleRepository
      .createQueryBuilder('raffle')
      .where('raffle.dateStart IN (:...dateStart)', { dateStart: ['2022-01-12', '2022-02-12']})
      .getRawMany();

파라미터의 값을 1개가 아니라 리스트로 넣을 수도 있습니다

WHERE 표현식 추가하기

2중 WHERE

Where 절은 위에서 썼으니 간단한건 넘어가고. Where 절은 이중으로도 쓸 수 있습니다.

const result = await this.raffleRepository
  .createQueryBuilder('raffle')
  .where('raffle.dateStart IN (:...dateStart)', { dateStart: ['2022-01-12', '2022-02-12'] })
  .andWhere('raffle.closedPrice = :price', { price: 1711032 })
  .getRawMany()
SELECT ... FROM users user WHERE user.firstName = 'Timber' AND user.lastName = 'Saw'

SQL문으로 따지면 Where a and b라고 보면 됩니다.

WHERE 조건 복수개로


const result = await this.raffleRepository
  .createQueryBuilder('raffle')
  .where('raffle.dateStart IN (:...dateStart)', { dateStart: ['2022-01-12', '2022-02-12'] })
  .andWhere('raffle.closedPrice = :price', { price: 1711032 })
  .getRawMany()

위에서 했었던 것 처럼 where In 문을 사용하여 리스트를 조건 값들로 넣을 수 있습니다.

Having 문

Having문은 일단 당장 안필요해서 패스
https://typeorm.io/select-query-builder#adding-having-expression

Order by 문

Order by도 이미 사용해서 패스
https://typeorm.io/select-query-builder#adding-order-by-expression

DISTINCT ON 문(Postgres 전용)

distinct on은 Postgres 만의 구문인데, 컬럼 x에서 고유(Distinct)한 값들만 추려낸 뒤, 이 컬럼 옆의 y 컬럼의 데이터를 가져올 수 있습니다.
https://www.postgresdba.com/bbs/board.php?bo_table=B10&wr_id=47

const result = await this.raffleRepository
.createQueryBuilder('raffle')
.distinctOn(['raffle.productId']).orderBy('raffle.productId')
.getMany();

Group by 문

이것도 패스!
https://typeorm.io/select-query-builder#adding-group-by-expression

Limit 문

const result = await this.raffleRepository.
      createQueryBuilder('raffle').limit(10).getMany();

특정 개수의 데이터만 가져오도록 할 때 사용합니다.

offset

limit 과 offset의 차이는 limit은 출력할 행의 총 개수를 말하고, offset은 몇 번째 행부터 출력할지 정하는 것입니다.

const result = await this.raffleRepository.
  createQueryBuilder('raffle').offset(10500).getMany();

위의 경우 10501번째 데이터부터 조회해달라는 의미입니다.

Joining relations

드디어 관계 조인 입니다!

공식문서의 예를 먼저 들겠습니다.

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"
import { Photo } from "./Photo"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @OneToMany((type) => Photo, (photo) => photo.user)
    photos: Photo[]
}
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"
import { User } from "./User"

@Entity()
export class Photo {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    url: string

    @ManyToOne((type) => User, (user) => user.photos)
    user: User
}

User 엔티티와 Photo 엔티티가 있습니다. User와 Photo는 1:N 관계인데요, User는 여러 사진들에 대한 정보를 가질 수 있기 때문입니다.

const user = await createQueryBuilder("user")
    .leftJoinAndSelect("user.photos", "photo")
    .where("user.name = :name", { name: "Timber" })
    .getOne()

이 경우 User 엔티티에 대해 user.photos를 기준으로 photo 엔티티를 조인할 수 있스니다.

관계 조인하기

이를 저의 프로젝트 사례로 들어보겠습니다.

const result = await this.raffleRepository
.createQueryBuilder('raffle')
.leftJoinAndSelect('raffle.product', 'product')
.limit(10)
.getMany()

raffle과 관계맺고 있는 product 정보를 10개만 가져오도록 했습니다.

{
        "createdAt": "2023-02-22T08:37:18.885Z",
        "updatedAt": "2023-02-22T08:37:18.885Z",
        "deletedAt": null,
        "raffleId": 4003,
        "dateStart": "2022-04-25",
        "dateEnd": "2022-06-27",
        "isClosed": true,
        "closedPrice": 1629504,
        "usersId": 23868,
        "productId": 3233,
        "product": {
            "createdAt": "2023-02-22T08:35:54.938Z",
            "updatedAt": "2023-02-22T08:35:54.938Z",
            "deletedAt": null,
            "productId": 3233,
            "productImage": "https://source.unsplash.com/random",
            "productModel": "#97df6a",
            "productName": "Mynah, indian",
            "productSize": 254,
            "productColor": "Maroon",
            "productCategory": "Alley",
            "releasePrice": 343305,
            "releaseDate": "2022-07"
        }
    },
...

여러 래플은 하나의 product를 참조할 수 있는 N:1 관계인 상황입니다. 위의 경우 이 래플이 참조하는 Mynah, indian이라는 상품의 이름을 잘 조회하고 있습니다.

관계 맺은 테이블에 조건문 걸기

그런데 여기서 product의 조건을 또 걸 수 있습니다. 상품 테이블과 관계 맺은 래플 정보를 가져오는데, 상품의 size가 260인 것만 가져오도록 설정합니다.

 const result = await this.raffleRepository
      .createQueryBuilder('raffle')
      .leftJoinAndSelect('raffle.product', 'product')
      .where('product.productSize = :size', { size: 260})
      .limit(10)
    .getMany()
[
    {
        "createdAt": "2023-02-22T08:36:57.121Z",
        "updatedAt": "2023-02-22T08:36:57.121Z",
        "deletedAt": null,
        "raffleId": 87,
        "dateStart": "2022-01-03",
        "dateEnd": "2022-04-28",
        "isClosed": true,
        "closedPrice": 578384,
        "usersId": 47931,
        "productId": 4292,
        "product": {
            "createdAt": "2023-02-22T08:36:03.119Z",
            "updatedAt": "2023-02-22T08:36:03.119Z",
            "deletedAt": null,
            "productId": 4292,
            "productImage": "https://source.unsplash.com/random",
            "productModel": "#1db170",
            "productName": "Cat, miner's",
            "productSize": 260,
            "productColor": "Goldenrod",
            "productCategory": "Place",
            "releasePrice": 406528,
            "releaseDate": "2021-03"
        }
    },
    {
        "createdAt": "2023-02-22T08:36:57.121Z",
        "updatedAt": "2023-02-22T08:36:57.121Z",
        "deletedAt": null,
        "raffleId": 121,
        "dateStart": "2022-02-12",
        "dateEnd": "2022-08-26",
        "isClosed": true,
        "closedPrice": 716161,
        "usersId": 42445,
        "productId": 8113,
        "product": {
            "createdAt": "2023-02-22T08:36:26.437Z",
            "updatedAt": "2023-02-22T08:36:26.437Z",
            "deletedAt": null,
            "productId": 8113,
            "productImage": "https://source.unsplash.com/random",
            "productModel": "#c96baa",
            "productName": "Trumpeter, green-winged",
            "productSize": 260,
            "productColor": "Indigo",
            "productCategory": "Parkway",
            "releasePrice": 12028,
            "releaseDate": "2020-12"
        }
    },
    ...

모든 래플을 가져오는 것이 아니라 래플의 -> 상품의 -> 사이즈가 260인 것만 가져왔습니다. 놀랍습니다.

Inner and left joins

Left 조인 대신 inner join을 하고싶다면 innerJoinAndSelect를 사용하면 됩니다.

const result = await this.raffleRepository
.createQueryBuilder('raffle')
.innerJoinAndSelect(
'raffle.product', 'product',
'product.productColor = :color', { color: 'Indigo'}
)
.where('raffle.closedPrice > :price', { price: 400000})
.limit(10)
.getMany()

Left 조인과 Inner 조인의 차이점이 뭘까요. Inner 조인은 product 데이터가 없는 래플은 반환하지 않느다는 점입니다. Left 조인은 있으면 있는대로 없으면 없는대로 데이터를 붙여서 리턴합니다. 따라서 Inner 조인이 좀 더 보수적입니다.

Join without selection

innerJoinAndSelect는 참조 테이블의 정보도 함께 조회하는 반면 innerJoin은 메인 테이블의 데이터만 조회하고 참조 테이블의 정보는 참조만 하고 가져오진 않습니다. 필터링만하고 데이터를 아낄 때 사용하면 좋을 것 같군요.

const result = await this.raffleRepository
.createQueryBuilder('raffle')
.innerJoin('raffle.product', 'product')
.where('raffle.dateStart > :date', {date: "2022-04-22"})
.getMany();
[
    {
        "createdAt": "2023-02-22T08:37:18.885Z",
        "updatedAt": "2023-02-22T08:37:18.885Z",
        "deletedAt": null,
        "raffleId": 4003,
        "dateStart": "2022-04-25",
        "dateEnd": "2022-06-27",
        "isClosed": true,
        "closedPrice": 1629504,
        "usersId": 23868,
        "productId": 3233
    },

inner 조인은 했지만 관계 테이블은 가져오지 않는 상황입니다.

    const result = await this.raffleRepository
      .createQueryBuilder('raffle')
      .innerJoin('raffle.product', 'product')
      .where('product.productSize > :size', {size: 280})
      .getMany();

참조하는 테이블의 프로퍼티를 조건문으로 사용해도 마찬가지입니다.

Joining any entity or table

패스

Joining and mapping functionality

megapass

Getting the generated query

SQL을 실행하는 것이 아니라 쿼리문 자체를 반환하게 만들 수도 있다. 이건 좀 멋진 기능인거 같다.

const result = await this.raffleRepository
.createQueryBuilder('raffle')
.where('raffle.closedPrice = :price', { price: 716161 })
.getSql();
SELECT "raffle"."createdAt" AS "raffle_createdAt", "raffle"."updatedAt" AS "raffle_updatedAt", "raffle"."deletedAt" AS
"raffle_deletedAt", "raffle"."raffleId" AS "raffle_raffleId", "raffle"."dateStart" AS "raffle_dateStart",
"raffle"."dateEnd" AS "raffle_dateEnd", "raffle"."isClosed" AS "raffle_isClosed", "raffle"."closedPrice" AS
"raffle_closedPrice", "raffle"."usersId" AS "raffle_usersId", "raffle"."productId" AS "raffle_productId" FROM "Raffle"
"raffle" WHERE ( "raffle"."closedPrice" = $1 ) AND ( "raffle"."deletedAt" IS NULL )
const users = await createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
.printSql()
.getMany()

이런 것도 있다. printSql은 디버깅할 때 유용하다고 한다.

raw 결과값 얻기

결과값은 2개의 유형이 있다고 했다, 1)entities와 2)raw results.

  1. entitity 결과값 얻는 메소드 : getOne, getMany -> ex) productId 전부 가져오기
  2. raw 결과값 얻는 메소드 : getRawOne, getRawMany -> ex) 총 합, 구하기

일단 entity 결과값 부터 얻어보자.

const result = await this.raffleRepository
.createQueryBuilder('raffle')
.select('raffle.closedPrice')
.where('raffle.closedPrice > :price', { price: 300000 })
.getMany()
[
    {
        "closedPrice": 1629504
    },
    {
        "closedPrice": 1148082
    },
    {
        "closedPrice": 808706
    },
    {
    ...

이런 식으로 조건에 해당하는 closedPrice 데이터만 가져왔다.

그렇다면 여기서 closedPrice의 총합을 구해보자.

const result = await this.raffleRepository
.createQueryBuilder('raffle')
.select("SUM(raffle.closedPrice)", "sum")
.where('raffle.closedPrice > :price', { price: 300000 })
.getRawOne()

중요한 점은 getOne()이 아니라 getRawOne()을 해야한다는 점이다. 그렇지 않으면 빈 배열만 반환하게 된다.

{
    "sum": "11021370862"
}

정말 유용한 것 같다.

const result = await this.raffleRepository
  .createQueryBuilder('raffle')
  .select('raffle.dateEnd')
  .addSelect('SUM(raffle.closedPrice)', 'sum')
  .groupBy('raffle.dateEnd')
  .getRawMany();

이런 방식으로 select문에 추가로 우리가 생성한 Raw 결과값을 가진 프로퍼티를 추가할 수 있다.

 {
        "raffle_dateEnd": "2022-11-13T15:00:00.000Z",
        "sum": "33669927"
    },
    {
        "raffle_dateEnd": "2022-10-09T15:00:00.000Z",
        "sum": "30947221"
    },

Streaming result data

stream을 할 수 있다고..?
스트리밍은 로데이터를 반환하는데, 우리는 entity 변환을 직접 해야한다고 한다.

스티리밍 기능을 사용하기 위해선 pg-query-stream 인스톨 해야한다고 한다.

npm i pg-query-stream --save

일단 패스

Using pagination

어플리케이션에서 pagination 기능은 필수다. pagination 슬라이더, 무한 스크롤 등을 위해 사용하면 유용한 기능이다.

take() pagination

const result = await this.raffleRepository
  .createQueryBuilder('raffle')
  .leftJoinAndSelect('raffle.product', 'product')
  .take(10)
  .getMany()

그럼 해당 데이터 10개만 가져오게 된다.

skip() pagination

const result = await this.raffleRepository
  .createQueryBuilder('raffle')
  .leftJoinAndSelect('raffle.product', 'product')
  .skip(10)
  .getMany()

이건 첫번째 10개를 제외하고 하는 것이다.

take() + skip()

둘 다 동시에 써도 된다.

take-skip vs. limit-offset ?

take, skip이 limit, offset은 비슷한 역할을 하는 것 처럼 보인다. 하지만 limit, offeset은 join 등 쿼리문이 복잡해지면 쓰기 힘들다고 한다. take, skip은 이를 방지하기 때문에 쿼리가 복잡해지면 limit, offset 대신 take, skip을 쓰면 된다.

Set locking

쿼리빌더는 1) optimistic lock과 2) pessimistic lock 모두 제공한다고 한다.

Lock 모드

DB 유형에 따라 Lock모드 지원 여부가 달라지니 여기서 참고하면 좋다.

  • Postgres: pessimistic_read, pessimistic_write, for_no_key_update, for_key_share
  • MySQL 8+: pessimistic_read, pessimistic_write
  • MySQL < 8, Maria DB: pessimistic_write
  • Cockroach: pessimistic_write (nowait only)

https://typeorm.io/select-query-builder#set-locking

pessimistic read lock

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .setLock("pessimistic_read")
    .getMany()

pessimistic write lock

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .setLock("pessimistic_write")
    .getMany()

dirty read

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .setLock("dirty_read")
    .getMany()

optimistic lock

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .setLock("optimistic", existUser.version)
    .getMany()

setOnLock

row가 lock이 발생했을 때 취할 행동을 정해줄 수 있다. 디폴트로 lock을 기다려주는 행동으로 설정되어 있다.

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .setLock("pessimistic_write")
    .setOnLocked("nowait")
    .getMany()

기다려주기 싫다면 setOnLocked('nowait')으로 설정해주거나

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .setLock("pessimistic_write")
    .setOnLocked("skip_locked")
    .getMany()

row을 스킵하고 싶다면 이렇게 해주면 된다.

커스텀 index 사용(MySQL 전용)

특정 인덱스를 제공할 수도 있다.

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .useIndex("my_index") // name of index
    .getMany()

Max 수행 시간

서버를 박살내기 싫다면 slow query를 드롭하게 할 수도 있다. 이건 서버 안정성에 도움이 될 것 같은 기능이다.

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .maxExecutionTime(1000) // milliseconds.
    .getMany()

부분 선택(Partial selection)

몇몇 엔티티의 프로퍼티만 선택할 수도 있다.

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .select(["user.id", "user.name"])
    .getMany()

이를 적용해보겠다.

const result = await this.raffleRepository
.createQueryBuilder('raffle')
.select(['raffle.raffleId', 'raffle.isClosed', 'raffle.closedPrice'])
.getMany()
[
    {
        "raffleId": 4003,
        "isClosed": true,
        "closedPrice": 1629504
    },
    {
        "raffleId": 4004,
        "isClosed": true,
        "closedPrice": 1148082
    },

이것이야 말로 내가 찾던 기능이다.

subquery 사용하기

from, where, join 절을 사용하여 서브쿼리를 생성할 수 있다. 서브쿼리는 쿼리문 안의 쿼리문이다.

const qb = await dataSource.getRepository(Post).createQueryBuilder("post")

const posts = qb
    .where(
        "post.title IN " +
            qb
                .subQuery()
                .select("user.name")
                .from(User, "user")
                .where("user.registered = :registered")
                .getQuery(),
    )
    .setParameter("registered", true)
    .getMany()

이런 식으로 사용한다고 한다.

const posts = await dataSource
    .getRepository(Post)
    .createQueryBuilder("post")
    .where((qb) => {
        const subQuery = qb
            .subQuery()
            .select("user.name")
            .from(User, "user")
            .where("user.registered = :registered")
            .getQuery()
        return "post.title IN " + subQuery
    })
    .setParameter("registered", true)
    .getMany()
    ```
더 우아하게 사용하려면 이렇게 하면 된다고 한다.

자세한 내용은 공식문서를 참고하자

# Hidden Columns

만약 `select: false`와 같은 컬럼을 가져오려 한다면, addSelect를 사용해야 한다.

```ts
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @Column({ select: false })
    password: string
}

엔티티 예시다. 마지막 컬럼이 select: false 상태로 되어있다.

const users = await dataSource
    .getRepository(User)
    .createQueryBuilder()
    .select("user.id", "id")
    .addSelect("user.password")
    .getMany()

일반적인 find 쿼리로는 못가져오는 비밀번호 데이터를 이렇게 하면 데이터를 가져올 수 있다.

Deleted rows

만약 @DeletedDateColumn 속성을 가진 컬럼을 가져오고 싶다면?

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @DeleteDateColumn()
    deletedAt?: Date
}
const users = await dataSource
    .getRepository(User)
    .createQueryBuilder()
    .select("user.id", "id")
    .withDeleted()
    .getMany()

이렇게 하면 된다. 원래는 못가져오는데, 이렇게 하면 가져올 소위 soft deleted된 로우를 가져올 수 있다.

Common table expression

패스

Time Travel Queries

CockroachDB 전용이다. 패스

728x90

댓글