ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MongoDB,NestJs 공백 제거, 공백포함 문자열 찾기 [검색 기능 고도화]
    Project/NYAM 2023. 2. 19. 16:07

    들어가기에 앞서 $in vs $or 의 관한 몽고디비 직원의 답변을 참고 하길 바란다.

    https://www.mongodb.com/community/forums/t/performance-of-or-vs-in/175513/5

     

    몽고 디비에는 다음과 같이 저장이 되어있다.

     

    store_name: "뽀르뚜아 외대점"

     

    사용자의 편의를 위한 검색 api를 만들고자 한다.

     

    검색은 다음과 같은 예시가 있을 것같다.

     

     

    1.뽀르뚜아 외대점 : 이름이 완전히 같은 경우

     

    2뽀르뚜아 : 이름의 일부만 있는 경우

     

    3.뽀르뚜아외대점 : 이름의 공백이 없는 경우

     

     

     

    방식은 두가지로 고민을 했다.

    1.

    모든 데이터를 부른후 공백을 제거하여 배열에 넣고

    사용자 입력의 모든 공백을 지우고 디비에서 부른 데이터와 비교하면서 같은지 찾는다. -> 2N 시간 이니 O(N)  을 예상한다.

     

    2. 정규 표현식을 이용한다 .  N 시간을 예상한다.

     

    2번의 경우는 타입스크립트에서 문자열 일치를 찾는 방식에 따라 메모리, 시간을 비교하는 블로그나 사이트가 있으니 찾아보길 바란다.

     

    나는 위의 두가지중 미래의 데이터양의 증가를 생각을 하여 2번으로 진행을 하고자 했으며

    공부의 목적도 컸다.

     

     

     

    1. 컨트롤러 단

    @Get('/admin/search')
      @Roles(Role.ADMIN)
      async store_admin_search_list_admin(@CurrentUser() user, @Query() query) {
        const list = await this.storeService.store_admin_search_list(
          new StoreAdminSearchQuery(query),
        );
    
        return { stores: list };
      }

    2.StoreAdminSearcghQuery class  변경전  RegExp flag 'g' 가 걸려있어

    /"찾고자하는 표현"/g 으로 날아간다.

    export class StoreAdminSearchQuery {
      store_active: boolean;
      campus_id: Types.ObjectId;
      owner_id: Types.ObjectId;
      block: boolean;
      type: string;
      store_name: string;
      constructor(data) {
        this.campus_id = data.campus_id;
        this.block = data.block;
        this.type = data.type;
        this.store_name = data.store_name;
        this.store_active = data.store_active;
      }
      query() {
        const query = {};
        if (this.store_active) query['store_active'] = this.store_active;
        if (this.campus_id) query['campus_id'] = this.campus_id;
        if (this.block) query['store_block'] = this.block;
        if (this.type) query['owner_type'] = this.type;
    
        if (this.store_name) query['store_name'] = new RegExp(this.store_name, 'g');
        return query;
      }
    }

     

    3. 서비스단

    async store_admin_search_list(query: StoreAdminSearchQuery) {
        const list = await this.storeModel.find(query.query());
        return list;
      }

     

    특이한점은 dto 에 query()가 들어가는 것이다.

    -> 외주를 받은 레거시 코드의 대부분이 dto 단에 query 또는 paging 이 들어가 있는 구조로 작성이 되어 있었으며,코드의 통일성을 위하여 오브젝트가 필요할때 마다 같은 방식으로 추가를 하거나 수정을 하고있다.

     

    이전의 spring layer 아키텍처를 사용하면서 dto 에는 로직이 들어가지 않는다라는 사실이 아직도 의문이긴 하다..

     

     

    2.StoreAdminSearcghQuery class  변경후

     

    export class StoreAdminSearchQuery {
      store_active: boolean;
      campus_id: Types.ObjectId;
      owner_id: Types.ObjectId;
      block: boolean;
      type: string;
      store_name: string;
      constructor(data) {
        this.campus_id = data.campus_id;
        this.block = data.block;
        this.type = data.type;
        this.store_active = data.store_active;
        this.store_name = data.store_name.trim();
      }
      query() {
        const query = {};
        if (this.store_active) query['store_active'] = this.store_active;
        if (this.campus_id) query['campus_id'] = this.campus_id;
        if (this.block) query['store_block'] = this.block;
        if (this.type) query['owner_type'] = this.type;
        // db.컬렉션.find({target : 'A'})
        if (this.store_name)
          query['store_name'] = {
            $in: [
              new RegExp(
                this.store_name + '|' + this.store_name.replace(/ /g, ''),
                'g',
              ),
              new RegExp(this.store_name.split('').join('\\s*'), 'g'),
              new RegExp('.*' + this.store_name + '.*', 'i'),
            ],
          };
        console.log(query);
        return query;
      }
    }

     

     

     

    몽고 디비의 비교연산자인 $or 를 사용 할려고 했지만, 테스트중 여라가지를 살펴보니 

    $in 의 성능이 $or 의 성능 보다 좋은점 하나의 필드에서 비교를 할때 사용하는점을 근거로 $in 을 사용했다.

     

     

Designed by Tistory.