[프로그래머스/SQL 고득점 Kit] SELECT

2024. 9. 17. 17:17코딩 테스트(Coding Test)/프로그래머스

평균 일일 대여 요금 구하기

나의 답)

SELECT ROUND(AVG(DAILY_FEE), 0) AS AVERAGE_FEE
FROM CAR_RENTAL_COMPANY_CAR
WHERE CAR_TYPE = 'SUV';

# CAR_RENTAL_COMPANY_CAR 테이블에서
# 자동차 종류가 'SUV'인 자동차들의
# 평균 일일 대여 요금 출력 -> 소수 첫 번째 자리에서 반올림 & 컬럼명은 AVERAGE_FEE

흉부외과 또는 일반외과 의사 목록 출력하기

나의 답)

SELECT DR_NAME, DR_ID, MCDP_CD, DATE_FORMAT(HIRE_YMD, "%Y-%m-%d") AS HIRE_YMD
FROM DOCTOR
WHERE MCDP_CD = 'CS' || MCDP_CD = 'GS'
ORDER BY HIRE_YMD DESC, DR_NAME ASC;

# DOCTOR 테이블에서
# 진료과가 흉부외과(CS) || 일반외과(GS)인 의사의
# 이름, 의사ID, 진료과, 고용일자 조회
# 고용일자를 기준으로 내림차순, 이름을 기준으로 오름차순

과일로 만든 아이스크림 고르기

나의 답)

SELECT fh.FLAVOR
FROM FIRST_HALF fh
INNER JOIN ICECREAM_INFO ii
ON fh.FLAVOR = ii.FLAVOR
WHERE TOTAL_ORDER > 3000 AND ii.INGREDIENT_TYPE = 'fruit_based'
ORDER BY TOTAL_ORDER DESC;

# 상반기 아이스크림의
# 총주문량 > 3,000 && 주성분 == '과일'인 아이스크림 맛을
# 총주문량 DESC

3월에 태어난 여성 회원 목록 출력하기

나의 답)

SELECT MEMBER_ID, MEMBER_NAME, GENDER, DATE_FORMAT(DATE_OF_BIRTH, "%Y-%m-%d") AS DATE_OF_BIRTH
FROM MEMBER_PROFILE
WHERE MONTH(DATE_OF_BIRTH) = 3 AND GENDER = 'W' AND TLNO IS NOT NULL
ORDER BY MEMBER_ID ASC;

# MEMBER_PROFILE 테이블에서
# 생일이 '3월'인 '여성'회원의
# ID, 이름, 성별, 생년월인 조회
# if) 전화번호 == NULL 출력x
# 회원ID 기준 ASC
# 날짜 출력형식 확인

서울에 위치한 식당 목록 출력하기

나의 답1)

SELECT ri.REST_ID, ri.REST_NAME, ri.FOOD_TYPE, ri.FAVORITES, ri.ADDRESS, ROUND(AVG(rr.REVIEW_SCORE), 2) AS SCORE
FROM REST_INFO ri, REST_REVIEW rr
WHERE ADDRESS LIKE '서울%' AND ri.REST_ID = rr.REST_ID
GROUP BY ri.REST_ID, ri.REST_NAME, ri.FOOD_TYPE, ri.FAVORITES, ri.ADDRESS
ORDER BY SCORE DESC, FAVORITES DESC;

# REST_INFO, REST_REVIEW 테이블에서
# '서울'에 위치한 식당들의
# 식당ID, 식당 이름, 음식 종류, 즐겨찾기수, 주소, 리뷰 평균 점수 조회
# 리뷰 평균 점수 -> 소수점 세 번째 자리에서 반올림
# 평균점수 DESC, 즐겨찾기수 DESC

평균을 내기 때문에 GROUP BY를 사용하여 나머지 요소들을 묶어줌

 

나의 답2)

SELECT ri.REST_ID, ri.REST_NAME, ri.FOOD_TYPE, ri.FAVORITES, ri.ADDRESS, ROUND(AVG(rr.REVIEW_SCORE), 2) AS SCORE
FROM REST_INFO ri
JOIN REST_REVIEW rr
ON ri.REST_ID = rr.REST_ID
WHERE ADDRESS LIKE '서울%'
GROUP BY ri.REST_ID, ri.REST_NAME, ri.FOOD_TYPE, ri.FAVORITES, ri.ADDRESS
ORDER BY SCORE DESC, FAVORITES DESC;

# REST_INFO, REST_REVIEW 테이블에서
# '서울'에 위치한 식당들의
# 식당ID, 식당 이름, 음식 종류, 즐겨찾기수, 주소, 리뷰 평균 점수 조회
# 리뷰 평균 점수 -> 소수점 세 번째 자리에서 반올림
# 평균점수 DESC, 즐겨찾기수 DESC

평균을 내기 때문에 GROUP BY를 사용하여 나머지 요소들을 묶어줌


조건에 부합하는 중고거래 댓글 조회하기

나의 답1)

SELECT ugb.TITLE, ugb.BOARD_ID, ugr.REPLY_ID, ugr.WRITER_ID, ugr.CONTENTS, DATE_FORMAT(ugr.CREATED_DATE, '%Y-%m-%d') AS CREATED_DATE
FROM USED_GOODS_BOARD ugb, USED_GOODS_REPLY ugr
WHERE DATE_FORMAT(ugb.CREATED_DATE, '%Y-%m') = '2022-10' AND ugb.BOARD_ID = ugr.BOARD_ID
ORDER BY CREATED_DATE ASC, ugb.TITLE ASC;

# USED_GOODS_BOARD, USED_GOODS_REPLY에서
# 2022년 10월에 작성된
# 게시글 제목, 게시글 ID, 댓글 ID, 댓글 작성자 ID, 댓글 내용, 댓글 작성일 조회
# 댓글 작성일 ASC, 게시글 제목 ASC

 

나의 답2)

SELECT ugb.TITLE, ugb.BOARD_ID, ugr.REPLY_ID, ugr.WRITER_ID, ugr.CONTENTS, DATE_FORMAT(ugr.CREATED_DATE, '%Y-%m-%d') AS CREATED_DATE
FROM USED_GOODS_BOARD ugb
JOIN USED_GOODS_REPLY ugr
ON ugb.BOARD_ID = ugr.BOARD_ID
WHERE DATE_FORMAT(ugb.CREATED_DATE, '%Y-%m') = '2022-10'
ORDER BY CREATED_DATE ASC, ugb.TITLE ASC;

# USED_GOODS_BOARD, USED_GOODS_REPLY에서
# 2022년 10월에 작성된
# 게시글 제목, 게시글 ID, 댓글 ID, 댓글 작성자 ID, 댓글 내용, 댓글 작성일 조회
# 댓글 작성일 ASC, 게시글 제목 ASC

인기있는 아이스크림

나의 답)

SELECT FLAVOR
FROM FIRST_HALF
ORDER BY TOTAL_ORDER DESC, SHIPMENT_ID ASC;

# FIRST_HALF에서
# 아이스크림의 맛을
# 총주문량 DESC, 출하 번호 ASC

강원도에 위치한 생상공장 목록 출력하기

나의 답)

SELECT FACTORY_ID, FACTORY_NAME, ADDRESS
FROM FOOD_FACTORY
WHERE ADDRESS LIKE '강원도%'
ORDER BY FACTORY_ID ASC;

# FOOD_FACTORY에서
# '강원도'에 위치한
# 공장 ID, 공장 이름, 주소 조회
# 공장 ID ASC

12세 이하인 여자 환자 목록 출력하기

나의 답)

SELECT PT_NAME, PT_NO, GEND_CD, AGE, IFNULL(TLNO, 'NONE') AS TLNO
FROM PATIENT
WHERE AGE <= 12 AND GEND_CD = 'W'
ORDER BY AGE DESC, PT_NAME ASC;

# PATIENT에서
# '12세 이하'인 '여자'환자의
# 환자이름, 환자번호, 성별코드, 나이, 전화번호 조회
# if) 전화번호 == NULL -> 'NONE'으로 출력
# 나이 DESC, 환자이름 ASC

조건에 맞는 도서 리스트 출력하기

나의 답)

SELECT BOOK_ID, DATE_FORMAT(PUBLISHED_DATE, '%Y-%m-%d') AS PUBLISHED_DATE
FROM BOOK
WHERE DATE_FORMAT(PUBLISHED_DATE, '%Y') = '2021' AND CATEGORY = '인문'
ORDER BY PUBLISHED_DATE ASC;

# BOOK에서
# '2021'년에 출판된 '인문'카테고리에 속하는
# 도서 ID, 출판일 출력
# 출판일 ASC

재구매가 일어난 상품과 회원 리스트 구하기

나의 답)

SELECT USER_ID, PRODUCT_ID
FROM ONLINE_SALE
GROUP BY USER_ID, PRODUCT_ID
HAVING COUNT(*) >= 2
ORDER BY USER_ID ASC, PRODUCT_ID DESC;

# ONLINE_SALE에서
# 동일한 회원이 동일한 상품을 재구매한 데이터를 구하여
# 재구매한 회원 ID, 재구매한 상품 ID 출력
# 회원 ID ASC, 상품 ID DESC

모든 레코드 조회하기

나의 답)

SELECT *
FROM ANIMAL_INS
ORDER BY ANIMAL_ID ASC;

# ANIMAL_INS에서
# *를
# ANIMAL_ID ASC;

역순 정렬하기

나의 답)

SELECT NAME, DATETIME
FROM ANIMAL_INS
ORDER BY ANIMAL_ID DESC;

# ANIMAL_INS에서
# 동물의 이름, 보호 시작일 조회
# ANIMAL_ID DESC

오프라인/온라인 판매 데이터 통합하기

나의 답)

SELECT DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DATE, PRODUCT_ID, USER_ID, SALES_AMOUNT
FROM ONLINE_SALE
WHERE DATE_FORMAT(SALES_DATE, '%Y-%m') = '2022-03'

UNION ALL

SELECT DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DATE, PRODUCT_ID, NULL AS USER_ID, SALES_AMOUNT
FROM OFFLINE_SALE
WHERE DATE_FORMAT(SALES_DATE, '%Y-%m') = '2022-03'

ORDER BY SALES_DATE ASC, PRODUCT_ID, USER_ID ASC;

# ONLINE_SALE, OFFLINE_SALE에서
# '2022-03'의 오프라인/온라인 상품 판매 데이터의
# 판매 날짜, 상품ID, 유저ID, 판매량 출력
# OFFLINE_SALE의 USER_ID는 'NULL'로 표시
# 판매일 ASC, 상품ID ASC, 유저ID ASC

아픈 동물 찾기

나의 답)

SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS
WHERE INTAKE_CONDITION = 'Sick'
ORDER BY ANIMAL_ID ASC;

# ANIMAL_INS에서
# '아픈' 동물의
# 아이디, 이름 조회
# 아이디 ASC

동물의 아이디와 이름

나의 답)

SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS
ORDER BY ANIMAL_ID ASC;

# ANIMAL_INS에서
# 모든 동물의 아이디, 이름 조회
# ANIMAL_ID ASC

여러 기준으로 정렬하기

나의 답)

SELECT ANIMAL_ID, NAME, DATETIME
FROM ANIMAL_INS
ORDER BY NAME ASC, DATETIME DESC;

# ANIMAL_INS에서
# 아이디, 이름, 보호 시작일 조회
# 이름 ASC, 보호 시작일 DESC

상위 n개 레코드

나의 답)

SELECT NAME
FROM ANIMAL_INS
ORDER BY DATETIME ASC
LIMIT 1;

# ANIMAL_INS에서
# LIMIT 1
# 이름
# 보호 시작일 ASC

조건에 맞는 회원 수 구하기

나의 답)

SELECT COUNT(*)
FROM USER_INFO
WHERE DATE_FORMAT(JOINED, '%Y') = 2021 AND AGE >= 20 AND AGE <= 29;

# USER_INFO에서
# '2021'년에 가입한 회원 중
# 나이 >= 20 && 나이 <= 29인 회원이
# 몇 명인지 출력

업그레이드 된 아이템 구하기

나의 답)

SELECT ii.ITEM_ID, ii.ITEM_NAME, ii.RARITY
FROM ITEM_INFO ii
JOIN ITEM_TREE it
ON ii.ITEM_ID = it.ITEM_ID
WHERE it.PARENT_ITEM_ID IN (SELECT ITEM_ID FROM ITEM_INFO WHERE RARITY = 'RARE')
ORDER BY ii.ITEM_ID DESC;

# ITEM_INFO, ITEM_TREE에서
# 아이템의 희귀도가 'RARE'인 아이템의
# 다음 업그레이드 아이템의
# 아이템ID, 아이템 명, 아이템의 희귀도 출력
# 아이템ID DESC

 

나의 답2)

SELECT ii2.ITEM_ID, ii2.ITEM_NAME, ii2.RARITY
FROM ITEM_INFO ii
JOIN ITEM_TREE it
ON ii.ITEM_ID = it.PARENT_ITEM_ID
JOIN ITEM_INFO ii2
ON it.ITEM_ID = ii2.ITEM_ID
WHERE ii.RARITY = 'RARE'
ORDER BY ii2.ITEM_ID DESC;

# ITEM_INFO, ITEM_TREE에서
# 아이템의 희귀도가 'RARE'인 아이템의
# 다음 업그레이드 아이템의
# 아이템ID, 아이템 명, 아이템의 희귀도 출력
# 아이템ID DESC

Python 개발자 찾기

나의 답)

SELECT ID, EMAIL, FIRST_NAME, LAST_NAME
FROM DEVELOPER_INFOS
WHERE SKILL_1 = 'Python' || SKILL_2 = 'Python' || SKILL_3 = 'Python'
ORDER BY ID ASC;

# DEVELOPER_INFOS에서
# 'Python' 스킬을 가진 개발자의
# ID, 이메일, 이름, 성 조회
# ID ASC

조건에 맞는 개발자 찾기

나의 답1)

SELECT ID, EMAIL, FIRST_NAME, LAST_NAME
FROM DEVELOPERS d
WHERE SKILL_CODE & (SELECT CODE FROM SKILLCODES WHERE NAME = 'Python')
OR SKILL_CODE & (SELECT CODE FROM SKILLCODES WHERE NAME = 'C#')
ORDER BY ID ASC;

# SKILLCODES, DEVELOPERS에서
# 'Python' || 'C#'스킬을 가진 개발자의
# ID, 이메일, 이름, 성 조회
# ID ASC

 

나의 답2)

SELECT ID, EMAIL, FIRST_NAME, LAST_NAME
FROM DEVELOPERS d
JOIN SKILLCODES s
ON (d.SKILL_CODE & s.CODE) != 0
WHERE s.NAME = 'Python' OR s.NAME = 'C#'
ORDER BY ID ASC;

# SKILLCODES, DEVELOPERS에서
# 'Python' || 'C#'스킬을 가진 개발자의
# ID, 이메일, 이름, 성 조회
# ID ASC

비트 연산을 하여 Python 혹은 C#이 있는지 확인함


잔챙이 잡은 수 구하기

나의 답)

SELECT COUNT(*) AS FISH_COUNT
FROM FISH_INFO
WHERE LENGTH IS NULL;

# FISH_INFO에서
# 길이가 <= 10인
# 물고기의 수 출력 -> FISH_COUNT

가장 큰 물고기 10마리 구하기

나의 답)

SELECT ID, LENGTH
FROM FISH_INFO
ORDER BY LENGTH DESC, ID ASC
LIMIT 10;

# FISH_INFO에서
# 가장 큰 물고기 10마리의
# ID, 길이 출력
# 길이 DESC, ID ASC

특정 물고기를 잡은 총 수 구하기

나의 답1)

SELECT COUNT(*) AS FISH_COUNT
FROM FISH_INFO fi, FISH_NAME_INFO fni
WHERE fi.FISH_TYPE = fni.FISH_TYPE AND FISH_NAME IN ('BASS', 'SNAPPER');

# FISH_INFO, FISH_NAME_INFO에서
# BASS, SNAPPER의 수 출력 -> FISH_COUNT

 

나의 답2)

SELECT COUNT(*) AS FISH_COUNT
FROM FISH_INFO fi
JOIN FISH_NAME_INFO fni
ON fi.FISH_TYPE = fni.FISH_TYPE
WHERE FISH_NAME = 'BASS' OR FISH_NAME = 'SNAPPER';

# FISH_INFO, FISH_NAME_INFO에서
# BASS, SNAPPER의 수 출력 -> FISH_COUNT

대장균들의 자식의 수 구하기

SELECT ed.ID, IFNULL(COUNT(ed1.ID), 0) AS CHILD_COUNT
FROM ECOLI_DATA ed
LEFT JOIN ECOLI_DATA ed1
ON ed.ID = ed1.PARENT_ID
GROUP BY ed.ID
ORDER BY ed.ID ASC;

# ECOLI_DATA에서
# ID, 자식의 수 출력
# if) 자식의 수 == NULL -> 0으로 출력
# ID ASC

대장균의 크기에 따라 분류하기1

나의 답)

SELECT ID, CASE WHEN SIZE_OF_COLONY <= 100 THEN 'LOW' WHEN SIZE_OF_COLONY <= 1000 THEN 'MEDIUM' ELSE 'HIGH' END AS SIZE
FROM ECOLI_DATA
ORDER BY ID ASC;

# ECOLI_DATA에서
# ID, SIZE 출력
# ID ASC;

특정 형질을 가지는 대장균 찾기

나의 답)

SELECT COUNT(*) AS COUNT
FROM ECOLI_DATA
WHERE (GENOTYPE & 2) = 0 -- 2번 형질을 보유하지 않음
  AND ((GENOTYPE & 1) = 1 OR (GENOTYPE & 4) = 4) -- 1번 형질 또는 3번 형질을 보유

부모의 형질을 모두 가지는 대장균 찾기

나의 답)

SELECT ed.ID, ed.GENOTYPE, ed1.GENOTYPE AS PARENT_GENOTYPE
FROM ECOLI_DATA ed
JOIN ECOLI_DATA ed1
ON ed.PARENT_ID = ed1.ID
WHERE (ed.GENOTYPE & ed1.GENOTYPE) = ed1.GENOTYPE
ORDER BY ed.ID ASC;

# ECOLI_DATA에서
# 부모의 형질을 모두 보유한 대장균의
# ID, 형질, 부모 대장균의 형질 출력
# ID ASC

대장균의 크기에 따라 분류하기2

나의 답1)

WITH Ranked_Ecoli AS (
    SELECT
        ID,
        SIZE_OF_COLONY,
        NTILE(4) OVER (ORDER BY SIZE_OF_COLONY DESC) AS tile_rank
    FROM ECOLI_DATA
)
SELECT
    ID,
    CASE
        WHEN tile_rank = 1 THEN 'CRITICAL'
        WHEN tile_rank = 2 THEN 'HIGH'
        WHEN tile_rank = 3 THEN 'MEDIUM'
        ELSE 'LOW'
    END AS COLONY_NAME
FROM Ranked_Ecoli
ORDER BY ID ASC;

# ECOLI_DATA에서
# ID, 분류된 이름(COLONY_NAME) 출력
# ID ASC

 

* NTILE(): 데이터를 지정한 숫자만큼 균등하게 나누는 역할을 한다.

* OVER: 함수가 적용될 데이터 범위를 정의한다.

 

나의 답2)

WITH Ranked_Ecoli AS (
    SELECT
        ID,
        SIZE_OF_COLONY,
        PERCENT_RANK() OVER (ORDER BY SIZE_OF_COLONY DESC) AS pct_rank
    FROM ECOLI_DATA
)
SELECT
    ID,
    CASE
        WHEN pct_rank <= 0.25 THEN 'CRITICAL'
        WHEN pct_rank <= 0.50 THEN 'HIGH'
        WHEN pct_rank <= 0.75 THEN 'MEDIUM'
        ELSE 'LOW'
    END AS COLONY_NAME
FROM Ranked_Ecoli
ORDER BY ID ASC;

# ECOLI_DATA에서
# ID, 분류된 이름(COLONY_NAME) 출력
# ID ASC

 

* PERCENT_RANK(): 각 값이 전체 집합에서 얼마나 상위에 있는지 백분율로 나타낸다.

 

나의 답3)

WITH Ranked_Ecoli AS (
    SELECT
        ID,
        SIZE_OF_COLONY,
        ROW_NUMBER() OVER (ORDER BY SIZE_OF_COLONY DESC) AS rn,
        COUNT(*) OVER () AS total_count
    FROM ECOLI_DATA
)
SELECT
    ID,
    CASE
        WHEN rn <= total_count / 4 THEN 'CRITICAL'
        WHEN rn <= total_count / 2 THEN 'HIGH'
        WHEN rn <= total_count * 3 / 4 THEN 'MEDIUM'
        ELSE 'LOW'
    END AS COLONY_NAME
FROM Ranked_Ecoli
ORDER BY ID ASC;

# ECOLI_DATA에서
# ID, 분류된 이름(COLONY_NAME) 출력
# ID ASC

 

* ROW_NUMBER(): 각 개체에 고유한 순번을 부여한다. 가장 큰 대장균이 1번, 그다음 대장균이 2번이 된다.

* COUNT(*) OVER () AS total_count: 대장균 개체의 총 개수를 total_count라는 별칭으로 저장한다.


특정 세대의 대장균 찾기

나의 답)

SELECT e3.ID
FROM ECOLI_DATA e1
JOIN ECOLI_DATA e2 ON e2.PARENT_ID = e1.ID
JOIN ECOLI_DATA e3 ON e3.PARENT_ID = e2.ID
WHERE e1.PARENT_ID IS NULL
ORDER BY e3.ID ASC;

# ECOLI_DATA에서
# '3세대' 대장균의
# ID 출력
# ID ASC;

멸종위기의 대장균 찾기

나의 답)

WITH RECURSIVE GENERATION_CTE AS (
    -- 최초의 1세대 부모를 찾습니다 (PARENT_ID가 NULL인 개체들)
    SELECT ID, 1 AS GENERATION
    FROM ECOLI_DATA
    WHERE PARENT_ID IS NULL
    UNION ALL
    -- 각 부모의 자식들에 대해 세대를 하나씩 증가시켜 연결합니다.
    SELECT E.ID, G.GENERATION + 1
    FROM ECOLI_DATA E
    JOIN GENERATION_CTE G ON E.PARENT_ID = G.ID
)
-- 이제 자식이 없는 개체들을 찾습니다.
SELECT COUNT(*) AS COUNT, GENERATION
FROM GENERATION_CTE G
LEFT JOIN ECOLI_DATA E ON G.ID = E.PARENT_ID
WHERE E.ID IS NULL -- 자식이 없는 개체들
GROUP BY GENERATION
ORDER BY GENERATION ASC;


# ECOLI_DATA에서
# 자식이 없는
# 개체의 수(COUNT), 세대(GENERATION) 출력
# 세대 ASC;

 

[해결 과정]

1.  각 세대(GENERATION)을 구분하기 위해 PARENT_ID와 ID의 관계를 기반으로 트리를 구성한다.

2. 자식이 없는 대장균을 찾아야 하므로, 각 개체가 다른 개체의 PARENT_ID로 사용되지 않는 경우를 찾아야 한다.

3. 세대를 정의하기 위해, 최초의 부모(PARENT_ID가 NULL인 개체들)가 1세대이고, 그들의 자식들이 2세대, 그 자식들이 3세대... 이런 식으로 세대 관계를 구성할 수 있다.