SQL 에서 QueryBuilder

김명일·2022년 5월 11일
0

스타트업 개발일지

목록 보기
2/10

입사한지 이삼주 동안, 코드리뷰와 약간의 수정, 그리고 코드 정리가 주 업무였다. 그 과정속에서 가장 힘들었던건 raw query를 사용하고 있었던 것이다.


Raw Query..

디비에 데이터를 요청하고 수정할 때마다, 매번 직접 SQL을 작성했다.

SELECT * FROM ... WHERE ...
UPDATE .. SET ... WHERE ...

근데 이게 여간 힘든게 아니었다. 기존에 prettier적용도 되어있지 않아서 코드를 보기란 여간 힘든게 아니었다. 또한 SQL이 직접 작성되어있다보니 매번 SQL을 읽으며 기능을 이해해야 했다. prettier적용 이후에도 문제였던게, SQL이 string이다 보니, prettier로 포맷팅될 경우, 애써 보기 편하게 만들어놓았던 SQL구문은 어그러졌다.

그래서 그나마 사용해본적 있던 mongoose와 같이 코드를 쓸 수 있다면 좋을텐데 라고 생각하며 찾아보기 시작했다. 당시에 개발 공부를 혼자서 해왔고, 주변에 물어볼 다른 개발자들도 없었기에 query builder나 orm, odm이라는 용어에 대해서도 몰라 mysql mongoose 이런식으로 검색했던 것 같다.

그러던중 query builderorm에 대해 알게되었고, 둘 중 나름대로 비교를 해보기 시작했다. 지금 생각하면 거의 고민없이 무조건 orm을 선택할 것 이지만 당시에는 하나도 몰랐기에 어떤걸 써야하지만 몇일 고민했던것 같다.

구글에 보면 orm의 사용을 피하라는 글들이 많이 있었다. 이유들은 sql이 아닌 orm에 대해 배우게 된다는 것, 그리고 쿼리가 어떻게 나가게되는지 보이지 않기 때문에, 의도치 않은 쿼리가 나가게 된다는 점 들을 얘기하고 있다. 그리고 orm에 대한 공부가 따로 필요하다는 점도 꼽았다.

이런 이유들을 보니,
'어 그럼 쿼리빌더를 사용해야겠다. 그러면 내가 직접 쿼리를 짜는대신 좀더 가독성 있고, 편하게 작성할 수 있겠다. 그리고 SQL도 배울 수 있겠네'
라고 생각하며 knex라는 쿼리빌더를 사용하며 기존에 SQL코드들을 개선해 나갔다.

지금와서 생각했을 때, 쿼리빌더를 사용하던 orm을 사용하던 남들의 글만 보고 선택하는게 아니라, 직접 사용해보고 조금 더 공부해보고 결정하며 선택에 명확한 이유를 찾았어야 한다고 생각한다.


불편함

기존 SQL들을 쿼리빌더로 수정해나가던 중, 계속해서 mongoose처럼 사용하고 싶다는 생각이 들었다. mongoose를 보면

Order.find({usr_id: 48291, odr_info_date: {$gte: "2022-05-06", $lte: "2022-05-10"}})

이런식으로 WHERE구문을 작성하는 등 기능들이 있었다.

그치만 쿼리빌더를 사용하니 조금씩 쿼리가 길어지는 불편함이 있었다.

querybuilder('orders')
.where({usr_id: 48291})
.andWhere('odr_info_date', '<=', "2022-05-10")
.andWhere('odr_info_date', '>=', "2022-05-06")

뒤에 join이나 offset, limit, order by 등이 들어가게되면 점점쿼리가 길어져보였다. 그리고 $in 이나 $gte, $lte, $and, $or와 같은 연산자들을 사용하여 쿼리를 좀 더 편하게 사용하고 싶었다. 그래서 커스텀쿼리들을 만들어 knex를 확장해 사용했다. 그 결과, 아래처럼 더 편하게 사용할 수 있었다.

// find(where, select, {join, sort, limit, skip})
 Orderinfo.find(
    {
      odr_info_rt_status,
      odr_info_status,
      odr_id,
      odr_info_id,
      $and: [
        sdate && { odr_info_date: { $gte: moment(sdate).format('YYYY-MM-DD 00:00:00') } },
        edate && { odr_info_date: { $lte: moment(edate).format('YYYY-MM-DD 23:59:59') } },
        str_name && { [Store.column('str_name')]: { $like: `%${str_name.trim()}%` } },
        usr_name && { [User.column('usr_name')]: { $like: `%${usr_name.trim()}%` } },
        prd_name && { [Product.column('prd_name')]: { $like: `%${prd_name.trim()}%` } },
      ].filter((val) => val),
    },
    ['*'],
    {
      join: [$.join(User, 'usr_id'), $.join(Store, 'str_id'), $.join(Product, 'prd_id')],
      sort: { odr_info_date: 1 },
    }
  );

당시에는 디비의 데이터를 가져왔을 때, 왜 별도로 맵핑하여 사용해야하는지 등에 대해서 잘 몰랐기에, 가져온 데이터를 그대로 반환거나 했었다. (그대로 반환하는 경우, 컬럼을 추가하거나 수정하는 등 테이블의 변화가 생기면 API스펙자체가 변하는 문제가 있다.)

사용해보진 않았지만, 만들어야할 서비스가 작은편이라면, 쿼리빌더를 사용해도 크게 어려움이 없을 것 같다. 오히려, 러닝커브가 orm보다 작아 편하게 사용할 수 있다는 생각이 들었다.

profile
주니어 백엔드 🐶🦶🏻📏

0개의 댓글