본문 바로가기
트러블슈팅

[supabase] enabled / 나는 테이블 3개 조인이 필요한데...

by dev__log 2024. 3. 22.

문제 상황

즐겨 찾는 화장실 페이지에서 즐겨찾기 한 화장실의 리뷰 평점을 보여주는 작업을 진행했다. 

 

supabase에서 컬럼을 외래키로 걸면 자동으로 인식하고 조인하듯이 2개의 테이블에서 조회할 수 있는데, 여기서 문제가 발생했다. 

 

일단 평점을 가져오기 위해서는 총 3개의 테이블이 필요했다. 

1. 화장실 정보 테이블 : toilet_location

2. 즐겨 찾는 화장실 테이블 : bookmark 

3. 리뷰 목록 데이터 테이블 : review_info 

 

supabase 공식 문서와 구글, 열심히 찾아다녔지만, 테이블 3개를 한 번에 조회하여 데이터를 가져오는 방법을 찾을 수 없었다.(내가 못 찾는 건가????/ 정말???)

튜터님께 도움을 요청해 enabled라는 옵션을 알게 됐다. 

 

데이터 조회를 한 번의 요청으로 끝내고 싶었지만, 조인 이슈 때문에 결국 나는 2번의 요청을 보내게 된다. 

 

해결 방법

1. 즐겨 찾는 화장실 목록 데이터는 toilet_location 테이블 + bookmark 테이블을 조인하여 데이터 조회

2. 리뷰 목록 데이터 : review_info 테이블에서 조회

이때 포인트는 2번-리뷰 목록 데이터를 조회할 때 1번-즐겨 찾는 화장실 데이터에서 id를 뽑아서 리뷰 테이블을 조회한다는 점이다. 

마치 조인 비슷하게 기능하도록 로직을 작성했다. 

 

방법은 enabled 옵션을 사용하는 것이다. 

공식 문서에 dependent-queries 목록에 있다. 

https://tanstack.com/query/latest/docs/framework/react/guides/dependent-queries

 

구현 로직

1. 즐겨 찾는 즐겨 찾는 화장실 목록 데이터는 toilet_location 테이블 + bookmark 테이블을 조인하여 즐겨 찾는 화장실 데이터 조회

2. 즐겨 찾는 화장실에 기반한 review_info 테이블 조회

 

 

먼저 즐겨 찾는 화장실 데이터를 조회한다.

select에는 원래 조회할 컬럼명이 들어가야 하지만, 외래키로 설정했을 경우 외래키로 설정된 테이블의 컬럼도 조회할 수 있다.  테이블명(컬럼명, 컬럼명 ...) 이렇게 조회하면 된다. 

select에 toilet_location 테이블에서 toilet_name과 toilet_address를 조회했다. 

//북마크 정보 조회 useQuery
const { data } = useQuery<Bookmark[]>({
    queryFn: () => getData(USER_ID),
    queryKey: [QUERY_KEY_BOOKMARK],
  });

//유저의 북마크 목록 조회
export const getData = async (user_id: string): Promise<Bookmark[]> => {
  try {
    const { data, error } = await supabase
      .from('bookmark')
      .select('*, toilet_location(toilet_name, toilet_address)')
      .eq('user_id', user_id);

    if (error) {
      console.error('Error fetching data:', error.message);
      return [];
    }

    return data as Bookmark[];
  } catch (error) {
    console.error(error);
    return [];
  }
};

 

 

위와 같이 즐겨 찾는 화장실 데이터를 조회한 후 리뷰 데이터를 조회하기 위해서 id 만 따로 변수에 담는다. 

const toiletIds = data?.map((bookmark) => bookmark.toilet_id) || []; //북마크한 화장실 데이터에서 toilet_id만 따로 담는다(리뷰 데이터 조회할 때 사용하기 위해서)

 

 

 

그러고 나서 화장실 id를 담은 변수를 리뷰 테이블을 조회할 때 값을 넘겨주고 조회하도록 했다. 

이때, 즐겨 찾는 화장실 데이터를 조회한 후에 리뷰 데이터를 조회할 수 있도록 하기 위해서 enabled 옵션을 사용했다. 

 

enabled: !!data  여기서 data(즐겨 찾는 화장실 데이터)가 조회돼서 값이 있을 때 리뷰 데이터 조회 요청을 실행한다. 

//리뷰 데이터 useQuery
  const { data: reviewData } = useQuery<ReviewRate[]>({
    queryFn: () => getReviewData(toiletIds), //화장실 id 넘김
    queryKey: [QUERY_KEY_REVIEW_RATE],
    enabled: !!data,
  });

 

 

 

toiletIds는 북마크가 여러 개일 수 있기 때문에. in으로 toiletIds에 해당하는 toilet_id 가 있는 데이터를 조회하도록 했다. 

//리뷰 데이터 조회
export const getReviewData = async (toiletIds: number[]): Promise<ReviewRate[]> => {
  try {
    const { data, error } = await supabase
      .from('review_info')
      .select('toilet_id, toilet_loc_rate, toilet_clean_rate, toilet_pop_rate')
      .in('toilet_id', toiletIds); //북마크 된 화장실 id 

    if (error) {
      console.error('Error fetching data:', error.message);
      return [];
    }

    return data as ReviewRate[];
  } catch (error) {
    console.error(error);
    return [];
  }
};

 

 

리뷰 평점 계산

calculateAverage를 좀 더 개선할 수 있을 것 같은데 하지 못했다.

일단 cnt로 수를 하나씩 더하는 부분과 avg 부분도... 개선하고 싶다. 

//리뷰 평점 계산
export const calculateAverage = (toiletId: number, reviewData: ReviewRate[]) => {
  let locRate = 0;
  let cleanRate = 0;
  let popRate = 0;
  let cnt = 0;

  reviewData?.forEach((el) => {
    if (el.toilet_id === toiletId) {
      locRate += el.toilet_loc_rate;
      cleanRate += el.toilet_clean_rate;
      popRate += el.toilet_pop_rate;
      cnt += 1;
    }
  });

  let avg = locRate / cnt + cleanRate / cnt + popRate / cnt / 3;
  return getStarRating(avg ? avg : 1);
};

export const getStarRating = (result: number): string => {
  switch (true) {
    case result === 5:
      return `⭐⭐⭐⭐⭐`;
    case result > 4:
      return `⭐⭐⭐⭐`;
    case result > 3:
      return `⭐⭐⭐`;
    case result > 2:
      return `⭐⭐`;
    default:
      return `⭐`;
  }
};

 

 

느낀점

일단 근본적인 해결이 아니라 아쉽지만, enabled와 데이터 조회 요청을 2번하여 원하는 결과는 얻을 수 있었다. 

이후에도 같은 상황이 생길 것 같아서 더 찾아봐야할 것 같다. 

enabled라는 유용한 옵션을 알게돼서 이후에도 많이 사용할 수 있을 것 같다. 

그리고 불필요한 요청도 방지할 수 있을 것 같다. 

 

 

 

[결과 화면]