3 분 소요

프로그래머스 사이트 링크

📌 문제 설명

다음은 식품의 정보를 담은 FOOD_PRODUCT 테이블입니다. FOOD_PRODUCT 테이블은 다음과 같으며 PRODUCT_ID, PRODUCT_NAME, PRODUCT_CD, CATEGORY, PRICE는 식품 ID, 식품 이름, 식품코드, 식품분류, 식품 가격을 의미합니다.

Column name Type Nullable
PRODUCT_ID VARCHAR(10) FALSE
PRODUCT_NAME VARCHAR(50) FALSE
PRODUCT_CD VARCHAR(10) TRUE
CATEGORY VARCHAR(10) TRUE
PRICE NUMBER TRUE


📌 문제

FOOD_PRODUCT 테이블에서 식품분류별로 가격이 제일 비싼 식품의 분류, 가격, 이름을 조회하는 SQL문을 작성해주세요. 이때 식품분류가 ‘과자’, ‘국’, ‘김치’, ‘식용유’인 경우만 출력시켜 주시고 결과는 식품 가격을 기준으로 내림차순 정렬해주세요.


📌 예시

FOOD_PRODUCT 테이블이 다음과 같을 때

PRODUCT_ID PRODUCT_NAME PRODUCT_CD CATEGORY PRICE
P0018 맛있는고추기름 CD_OL00008 식용유 6100
P0019 맛있는카놀라유 CD_OL00009 식용유 5100
P0020 맛있는산초유 CD_OL00010 식용유 6500
P0021 맛있는케첩 CD_SC00001 소스 4500
P0022 맛있는마요네즈 CD_SC00002 소스 4700
P0039 맛있는황도 CD_CN00008 4100
P0040 맛있는명이나물 CD_CN00009 3500
P0041 맛있는보리차 CD_TE00010 3400
P0042 맛있는메밀차 CD_TE00001 3500
P0099 맛있는맛동산 CD_CK00002 과자 1800

SQL을 실행하면 다음과 같이 출력되어야 합니다.

CATEGORY MAX_PRICE PRODUCT_NAME
식용유 6500 맛있는산초유
과자 1800 맛있는맛동산

코드

ver(1) - 다중 컬럼 IN 서브쿼리 활용

핵심 아이디어: GROUP BY로 카테고리별 최대 가격을 먼저 구한 뒤, (CATEGORY, PRICE) 쌍이 서브쿼리 결과와 일치하는 행만 메인 쿼리에서 추출합니다. 가장 직관적으로 많이 사용하는 방식입니다.

-- 메인 쿼리: 서브쿼리에서 구한 (카테고리, 최대가격) 쌍과 일치하는 행을 조회
SELECT CATEGORY, PRICE AS MAX_PRICE, PRODUCT_NAME
FROM FOOD_PRODUCT
WHERE (CATEGORY, PRICE) IN (
    -- 서브쿼리: 4개 카테고리에 대해 카테고리별 최대 가격을 집계
    SELECT CATEGORY, MAX(PRICE)
    FROM FOOD_PRODUCT
    GROUP BY CATEGORY
    HAVING CATEGORY IN ('과자', '국', '김치', '식용유') -- 원하는 카테고리만 필터링
)
ORDER BY MAX_PRICE DESC; -- 가격 내림차순 정렬

💡 Tip. WHERE 대신 HAVING을 서브쿼리에서 사용한 이유는, GROUP BY 이후 집계된 결과에 조건을 걸어야 하기 때문입니다. 물론 WHERE CATEGORY IN (...)을 서브쿼리의 GROUP BY 앞에 써도 결과는 동일합니다. 단, 그 경우엔 먼저 행을 필터링한 뒤 집계하므로 처리 순서가 달라집니다.


ver(2) - Window Function (RANK() OVER) 활용

핵심 아이디어: RANK() 윈도우 함수로 카테고리(PARTITION) 내에서 가격 순위를 매기고, 순위가 1인 행만 추출합니다. 동점(같은 가격)이 여러 개 있어도 모두 1위로 처리되어 누락 없이 결과를 가져올 수 있습니다.

-- CTE: 카테고리별 가격 순위를 미리 계산해 임시 테이블로 정의
WITH RankedProducts AS (
    SELECT
        CATEGORY,
        PRICE AS MAX_PRICE,
        PRODUCT_NAME,
        -- PARTITION BY: 카테고리마다 독립적으로 순위를 매김
        -- ORDER BY PRICE DESC: 가격이 높을수록 낮은 순위 번호(1위)
        RANK() OVER (PARTITION BY CATEGORY ORDER BY PRICE DESC) AS rnk
    FROM FOOD_PRODUCT
    WHERE CATEGORY IN ('과자', '국', '김치', '식용유') -- 집계 전 미리 필터링
)

-- 메인 쿼리: CTE에서 각 카테고리 1위(가장 비싼 식품)만 추출
SELECT CATEGORY, MAX_PRICE, PRODUCT_NAME
FROM RankedProducts
WHERE rnk = 1
ORDER BY MAX_PRICE DESC;

⚠️ 주의. RANK()는 동점이 있을 경우 같은 순위를 부여하고 다음 순위를 건너뜁니다(예: 1, 1, 3). 반면 ROW_NUMBER()는 동점이어도 순위를 하나씩 부여하므로(예: 1, 2, 3), 동점인 행 중 하나만 가져옵니다. 이 문제처럼 동점인 행을 모두 가져와야 할 때RANK()를 사용하는 것이 안전합니다.


ver(3) - 상관 서브쿼리 (Correlated Subquery) 활용 💡

핵심 아이디어: 메인 쿼리의 각 행을 검사할 때마다, 해당 행의 카테고리(A.CATEGORY)를 기준으로 서브쿼리가 최대 가격을 동적으로 계산해 비교합니다. 다중 컬럼 IN이나 윈도우 함수 없이도 동일한 결과를 낼 수 있는 방법입니다.

-- 메인 쿼리: 각 행의 PRICE가 해당 카테고리의 최대 가격과 같은 행만 선택
SELECT CATEGORY, PRICE AS MAX_PRICE, PRODUCT_NAME
FROM FOOD_PRODUCT AS A
WHERE PRICE = (
    -- 상관 서브쿼리: 메인 쿼리의 현재 행(A)의 카테고리를 참조해 최대 가격을 계산
    -- A.CATEGORY = B.CATEGORY 조건으로 메인 쿼리와 서브쿼리가 연결됨
    SELECT MAX(PRICE)
    FROM FOOD_PRODUCT AS B
    WHERE A.CATEGORY = B.CATEGORY -- 핵심: 현재 행의 카테고리로 범위를 한정
)
AND CATEGORY IN ('과자', '국', '김치', '식용유') -- 원하는 카테고리만 최종 필터링
ORDER BY MAX_PRICE DESC;

⚠️ 주의. 상관 서브쿼리는 메인 쿼리의 행마다 서브쿼리를 반복 실행합니다. 테이블 데이터가 적은 경우엔 문제없지만, 데이터가 많아질수록 ver(1), ver(2)에 비해 성능이 저하될 수 있습니다. 실무에서는 데이터 규모를 고려해 쿼리 방식을 선택하는 것이 좋습니다.


📊 풀이법 비교 요약

  ver(1) 다중 IN 서브쿼리 ver(2) RANK() 윈도우 함수 ver(3) 상관 서브쿼리
가독성 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
성능 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐
동점 처리 ✅ 자동 ✅ RANK() 사용 시 ✅ 자동
모던 문법 보통 윈도우 함수 (MySQL 8.0+) 보통

댓글남기기