[프로그래머스] (MySQL) 83. 업그레이드 할 수 없는 아이템 구하기

최재원's avatar
Oct 11, 2025
[프로그래머스] (MySQL) 83. 업그레이드 할 수 없는 아이템 구하기
💡

문제 설명

어느 한 게임에서 사용되는 아이템들은 업그레이드가 가능합니다.
'ITEM_A'->'ITEM_B'와 같이 업그레이드가 가능할 때
'ITEM_A'를 'ITEM_B'의 PARENT 아이템,
PARENT 아이템이 없는 아이템을 ROOT 아이템이라고 합니다.
예를 들어 'ITEM_A'->'ITEM_B'->'ITEM_C' 와 같이 업그레이드가 가능한 아이템이 있다면
'ITEM_C'의 PARENT 아이템은 'ITEM_B'
'ITEM_B'의 PARENT 아이템은 'ITEM_A'
ROOT 아이템은 'ITEM_A'가 됩니다.
다음은 해당 게임에서 사용되는 아이템 정보를 담은 ITEM_INFO 테이블과 아이템 관계를 나타낸 ITEM_TREE 테이블입니다.
ITEM_INFO 테이블은 다음과 같으며, ITEM_IDITEM_NAMERARITYPRICE는 각각 아이템 ID, 아이템 명, 아이템의 희귀도, 아이템의 가격을 나타냅니다.
Column name
Type
Nullable
ITEM_ID
INTEGER
FALSE
ITEM_NAME
VARCHAR(N)
FALSE
RARITY
INTEGER
FALSE
PRICE
INTEGER
FALSE
ITEM_TREE 테이블은 다음과 같으며, ITEM_IDPARENT_ITEM_ID는 각각 아이템 ID, PARENT 아이템의 ID를 나타냅니다.
Column name
Type
Nullable
ITEM_ID
INTEGER
FALSE
PARENT_ITEM_ID
INTEGER
TRUE
단, 각 아이템들은 오직 하나의 PARENT 아이템 ID 를 가지며, ROOT 아이템의 PARENT 아이템 ID 는 NULL 입니다.
ROOT 아이템이 없는 경우는 존재하지 않습니다.
💡

문제

더 이상 업그레이드할 수 없는 아이템의 아이템 ID(ITEM_ID), 아이템 명(ITEM_NAME), 아이템의 희귀도(RARITY)를 출력하는 SQL 문을 작성해 주세요. 이때 결과는 아이템 ID를 기준으로 내림차순 정렬해 주세요.

예시

예를 들어 ITEM_INFO 테이블이 다음과 같고
ITEM_ID
ITEM_NAME
RARITY
PRICE
0
ITEM_A
RARE
10000
1
ITEM_B
RARE
9000
2
ITEM_C
LEGEND
11000
3
ITEM_D
RARE
10000
4
ITEM_E
RARE
12000
ITEM_TREE 테이블이 다음과 같다면
ITEM_ID
PARENT_ITEM_ID
0
NULL
1
0
2
0
3
1
4
1
'ITEM_A' 는 'ITEM_B', 'ITEM_C' 로 업그레이드가 가능하며 'ITEM_B' 는 'ITEM_D', 'ITEM_E' 로 업그레이드가 가능합니다. 'ITEM_C', 'ITEM_D', 'ITEM_E' 는 더 이상 업그레이드가 가능하지 않으므로 결과는 다음과 같이 나와야 합니다.
ITEM_ID
ITEM_NAME
RARITY
4
ITEM_E
RARE
3
ITEM_D
RARE
2
ITEM_C
LEGEND
 

코드

SELECT ITEM_ID, ITEM_NAME, RARITY FROM ITEM_INFO II WHERE NOT EXISTS ( SELECT 1 FROM ITEM_TREE IT WHERE II.ITEM_ID = IT.PARENT_ITEM_ID ) ORDER BY ITEM_ID DESC;
notion image

 

SQL의 스코프(Scope) 규칙

이러한 접근이 가능한 것은 SQL의 변수 스코프(Variable Scope) 규칙 덕분입니다.
  1. 내부 스코프는 외부 스코프에 접근 가능: 서브쿼리(내부 쿼리)는 외부 쿼리(상위 스코프) 내부에 정의되므로, 내부 스코프는 상위 스코프에 정의된 요소(테이블, 칼럼)에 접근할 수 있습니다.
  1. 테이블 별칭(Alias) 사용: 외부 쿼리의 테이블에 별칭(Customers c)을 지정하면, 서브쿼리 내부에서 이 별칭을 사용하여 외부 테이블의 칼럼(c.customer_id)에 명확하게 접근할 수 있습니다. 이것은 필수적인 프로그래밍 패턴입니다.
  1. 처리 순서: 데이터베이스 관리 시스템(DBMS)은 쿼리를 실행할 때 외부 쿼리에서 한 행을 선택한 후, 해당 행의 정보를 가지고 서브쿼리를 실행하는 방식으로 처리 순서를 정의합니다.

⚠️ 주의할 점 (FROM 절 접근 불가)

서브쿼리가 외부 쿼리의 FROM 절에 있는 테이블에 접근하는 것은 맞지만, 이는 WHERE, SELECT, 또는 HAVING 절에 위치한 서브쿼리에 한해서입니다.
  • FROM에 사용되는 서브쿼리(인라인 뷰/Derived Table)는 독립적으로 먼저 실행되어 하나의 임시 테이블을 생성합니다. 따라서 FROM 절에 있는 서브쿼리는 외부 쿼리의 테이블에 직접 접근할 수 없습니다. 이 서브쿼리는 일반 서브쿼리(비상관 서브쿼리)의 형태로 동작합니다.
 
쿼리 코드
SELECT c.customer_id, c.name FROM Customers c -- 외부 쿼리 (Customers 테이블의 각 행 c에 대해 실행) WHERE NOT EXISTS ( SELECT 1 -- 서브쿼리 (존재 여부만 확인하므로 SELECT * 대신 '1'을 사용하는 것이 효율적) FROM Orders o WHERE o.customer_id = c.customer_id -- 핵심: 상관 조건 (외부 c.customer_id와 내부 o.customer_id가 일치하는지 확인) );

(NOT IN) VS (NOT EXISTS)

NOT INNOT EXISTS의 차이를 깔끔하고 효율적으로 요약해 드리겠습니다.
구분
NOT IN
NOT EXISTS
핵심 동작
서브쿼리의 전체 결과 집합과 외부 값을 일일이 비교
서브쿼리 내에 관련 행이 존재하는지 여부만 확인 (상관 서브쿼리)
NULL 값
서브쿼리 결과에 NULL이 하나라도 있으면, 결과가 부정확해짐 (대부분의 행을 제외)
NULL 값 문제에서 자유로움 (행 존재 여부만 따짐)
성능 (대부분)
서브쿼리 결과를 메모리/임시 테이블에 저장 후 처리 → 대용량에서 상대적으로 느림
인덱스를 활용하여 빠른 검색 중단(Short-Circuiting) 가능 → 대부분의 경우 더 빠르고 안정적임
권장 사용
서브쿼리 결과가 매우 작고 NULL이 없는 것이 확실할 때 (가독성)
대부분의 경우 (특히 대용량 데이터에서) → 안정성과 성능 우위
결론: 안정성과 성능을 위해 **NOT EXISTS**를 사용하는 것이 SQL 개발의 모범 사례입니다. 👍
Share article

jjack1