[프로그래머스] ⭐⭐⭐⭐ 식품분류별 가장 비싼 식품의 정보 조회하기 (MySQL)
📌 문제 설명
다음은 식품의 정보를 담은 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+) | 보통 |
댓글남기기