NestJS API SQL query 모듈화

장유림·2025년 4월 6일

jz_project

목록 보기
3/7

ChatGPT와의 대화

getPostInfo를 QueryBuilder 기반으로 만들고, 여러 postId를 한 번에 넘겨서 IN (...)으로 조회하는 방식은 "모듈화"와 "성능"을 모두 잡는 절충안이에요. 왜냐하면:

✅ 장점 요약

  • 모듈화 getPostInfos(postIds: number[])처럼 독립 함수로 만들어서 재사용 가능
  • 성능 여러 개를 한 번에 IN (...)으로 가져오기 때문에 SQL 1번만 실행함
  • 유지보수 복잡한 조인 로직은 한곳에만 존재하니까 수정도 쉬움

✅ getPostInfos(postIds: number[]) 함수 만들기

async getPostInfos(postIds: number[]): Promise<{
  postId: number;
  postDatetime: Date;
  decoCard: string;
  enterComp: string;
  groupName: string;
  memberName: string;
  collectionName: string;
  nickname: string;
  likeCount: number;
}[]> {
  if (postIds.length === 0) return [];

  const result = await this.dataSource.query(`
    SELECT 
      p.id AS postId,
      p.postDatetime, 
      dc.decoCard,
      a.entertainmentCompany AS enterComp,
      a.groupName,
      pc.memberName,
      pc.collectionName,
      u.nickname,
      COUNT(l2.post_id) AS likeCount
    FROM post p
    INNER JOIN user u ON p.userId = u.id
    INNER JOIN deco_card dc ON dc.postId = p.id
    INNER JOIN photo_card pc ON dc.photoCardId = pc.id
    INNER JOIN artist a ON pc.photoCard = a.photoCard
    LEFT JOIN likes l2 ON l2.post_id = p.id
    WHERE p.id IN (${postIds.map(() => '?').join(',')})
    GROUP BY p.id, p.postDatetime, dc.decoCard, a.entertainmentCompany, a.groupName, pc.memberName, pc.collectionName, u.nickname
  `, postIds);

  return result.map((row) => ({
    postId: row.postId,
    postDatetime: row.postDatetime,
    decoCard: row.decoCard,
    enterComp: row.enterComp,
    groupName: row.groupName,
    memberName: row.memberName,
    collectionName: row.collectionName,
    nickname: row.nickname,
    likeCount: Number(row.likeCount),
  }));
}

✅ getPostsLiked()에서 적용하기

async getPostsLiked(userId: number, sort: 'oldest' | 'newest' | 'most_liked' | 'least_liked' = 'newest') {
  const user = await this.usersRepository.findOne({ where: { id: userId } });
  if (!user) {
    throw new Error('사용자를 찾을 수 없습니다.');
  }

  let orderBy = '';
  switch (sort) {
    case 'oldest':
      orderBy = 'p.postDatetime ASC';
      break;
    case 'newest':
      orderBy = 'p.postDatetime DESC';
      break;
    case 'most_liked':
      orderBy = 'likeCount DESC';
      break;
    case 'least_liked':
      orderBy = 'likeCount ASC';
      break;
  }

  const postIdRows = await this.dataSource.query(`
    SELECT p.id AS postId
    FROM post p
    INNER JOIN likes l ON l.post_id = p.id
    WHERE l.user_id = ?
    ORDER BY ${orderBy.replace('likeCount', '(SELECT COUNT(*) FROM likes WHERE post_id = p.id)')}
    LIMIT 5
  `, [userId]);

  const postIds = postIdRows.map((row) => row.postId);
  const postInfos = await this.getPostInfos(postIds);

  // 정렬 기준이 likeCount 관련이면 JS에서 정렬 한 번 더 확인
  if (sort === 'most_liked') {
    postInfos.sort((a, b) => b.likeCount - a.likeCount);
  } else if (sort === 'least_liked') {
    postInfos.sort((a, b) => a.likeCount - b.likeCount);
  }

  return {
    'like-list': postInfos,
  };
}

이 부분 로직 다시 한 번 확인

if (sort === 'most_liked') {
  postInfos.sort((a, b) => b.likeCount - a.likeCount);
} else if (sort === 'least_liked') {
  postInfos.sort((a, b) => a.likeCount - b.likeCount);
}

🧩 핵심 로직 설명

구문의미결과
a.likeCount - b.likeCounta가 b보다 좋아요가 적으면 음수, 같으면 0, 많으면 양수오름차순 정렬
b.likeCount - a.likeCount반대로 계산내림차순 정렬

1. sort === 'most_liked'

postInfos.sort((a, b) => b.likeCount - a.likeCount);

좋아요 수가 큰 것부터 작은 순으로 정렬
즉, 좋아요 많이 받은 포스트가 위에 옴

🧪 예:

[{ id: 1, likeCount: 10 }, { id: 2, likeCount: 5 }]

2. sort === 'least_liked'

postInfos.sort((a, b) => a.likeCount - b.likeCount);

좋아요 수가 작은 것부터 큰 순으로 정렬
즉, 좋아요 적게 받은 포스트가 위에 옴

🧪 예:

[{ id: 2, likeCount: 5 }, { id: 1, likeCount: 10 }]

💡 전체 구조 흐름도

getPostsLiked(userId, sort)
       │
       ├── DB에서 user가 좋아요 누른 postId 목록만 가져옴 (LIMIT 5)
       │
       ├── postIds → getPostInfos(postIds) 호출
       │         └─> DB에서 post 정보 + like 수 한 번에 조회
       │
       └── 결과를 정렬 후 프론트로 반환

✅ 이 쿼리에서 LIMIT 5의 의미

const postIdRows: { postId: number }[] = await this.dataSource.query(`
  SELECT p.id AS postId
  FROM post p
  INNER JOIN likes l ON l.post_id = p.id
  WHERE l.user_id = ?
  ORDER BY ${orderBy}
  LIMIT 5
`, [userId]);

🧠 왜 이게 필요한가?

  1. 페이징 또는 미리보기 용도일 수 있어요
    예: 홈화면에서 "내가 좋아요 누른 최근 게시물 5개 보여주기"

예: 무한 스크롤 초기 5개 불러오기

  1. 성능 최적화
    사용자가 수백 개의 post에 좋아요 눌렀을 경우, 한 번에 전부 가져오면 무거움

먼저 5개만 가져오고, 그 이후는 다음 페이지에서 추가 요청 (페이징) 하는 구조가 일반적이에요

우리 프로젝트의 경우 무한스크롤을 적용할 것이므로,
offset 기반 무한 스크롤을 구현하고자 한다.

OFFSET 방식: 페이지당 개수(limit)와 현재 페이지(offset)를 기준으로 슬라이싱

무한 스크롤 방식(offset/limit) 적용은 다음 포스트에 이어서...

+) 추가

postInfos를 다른 함수에서 사용할 때 타입을 알려주는 코드

profile
환영합니다

0개의 댓글