https://school.programmers.co.kr/learn/challenges?tab=sql_practice_kit
소프트웨어 마에스트로 코딩테스트에 SQL 1문제가 출제되어 준비를 위해 프로그래머스 고득점 Kit문제를 쭉 풀이할 예정입니다.
(이후 해커랭크 Medium 문제까지 풀이 계획)
모든 문제는 MySQL을 기준으로 작성했습니다.
SELECT ROUND(AVG(daily_fee)) as 'AVERAGE_FEE'
FROM CAR_RENTAL_COMPANY_CAR
WHERE car_type = 'SUV'
반올림에서는 ROUND(값, 자릿수) 사용 , 자릿수가 0이면 생략해도 됨
SELECT BOOK_ID, DATE_FORMAT(PUBLISHED_DATE,'%Y-%m-%d') AS PUBLISHED_DATE
FROM BOOK
WHERE CATEGORY = '인문' AND YEAR(PUBLISHED_DATE)=2021
ORDER BY PUBLISHED_DATE
DATE 형식에 YEAR()로 연도만 추출 가능.
DATE_FORMAT으로 형식 변경 가능
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
IFNULL(Column명, "Null일 경우 대체 값"): 칼럼값이 NULL일때 대체할 값을 넣을 수 있다.
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
DATE 형식에 MONTH()로 월만 추출 가능.
DATE_FORMAT으로 형식 변경 가능(y만 대문자 주의)
SELECT FLAVOR
FROM FIRST_HALF
ORDER BY TOTAL_ORDER DESC, SHIPMENT_ID ASC
SELECT DR_NAME, DR_ID, MCDP_CD, DATE_FORMAT(HIRE_YMD,'%Y-%m-%d') AS HIRE_YMD
FROM DOCTOR
WHERE MCDP_CD = 'CS' OR MCDP_CD = 'GS'
ORDER BY HIRE_YMD DESC, DR_NAME ASC
SELECT UB.TITLE, UB.BOARD_ID, UR.REPLY_ID, UR.WRITER_ID, UR.CONTENTS, DATE_FORMAT(UR.CREATED_DATE, "%Y-%m-%d") AS CREATED_DATE
FROM USED_GOODS_BOARD AS UB
JOIN USED_GOODS_REPLY AS UR ON (UB.BOARD_ID = UR.BOARD_ID)
WHERE YEAR(UB.CREATED_DATE)=2022 AND MONTH(UB.CREATED_DATE)=10
ORDER BY UR.CREATED_DATE, UB.TITLE
위에서 ON (UB.BOARD_ID = UR.BOARD_ID)
를 USING (BOARD_ID)
로 변경 가능
FROM 테이블1, 테이블2
은 FROM 테이블1 INNER JOIN 테이블2
와 같다.
표준 SQL과는 달리 MySQL에서는 JOIN, INNER JOIN, CROSS JOIN이 모두 같은 의미로 사용
SELECT FLAVOR
FROM FIRST_HALF AS FH
INNER JOIN ICECREAM_INFO AS II USING (FLAVOR)
WHERE FH.TOTAL_ORDER > 3000 AND II.INGREDIENT_TYPE='fruit_based'
ORDER BY FH.TOTAL_ORDER DESC
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 AS RI
INNER JOIN REST_REVIEW AS RR USING (REST_ID)
WHERE RI.ADDRESS LIKE '서울%'
GROUP BY RI.REST_ID
ORDER BY SCORE DESC, RI.FAVORITES DESC
LIKE %서울%
로 작성하면 틀린다.
AVG 이후 ROUND를 적용해서 소숫점 반올림
HAVING => GROUP BY 이후에 테이블에 대해 조건 설정
SELECT FACTORY_ID, FACTORY_NAME, ADDRESS
FROM FOOD_FACTORY
WHERE ADDRESS LIKE "강원도%"
ORDER BY FACTORY_ID
SELECT USER_ID, PRODUCT_ID
FROM ONLINE_SALE
GROUP BY USER_ID, PRODUCT_ID
HAVING COUNT(*) >= 2
ORDER BY USER_ID, PRODUCT_ID DESC
SELECT *
FROM ANIMAL_INS
ORDER BY ANIMAL_ID
(SELECT DATE_FORMAT(SALES_DATE,"%Y-%m-%d") AS SALES_DATE, PRODUCT_ID, USER_ID, SALES_AMOUNT
FROM ONLINE_SALE
WHERE YEAR(SALES_DATE)=2022 AND MONTH(SALES_DATE)=3
)
UNION
(SELECT DATE_FORMAT(SALES_DATE,"%Y-%m-%d") AS SALES_DATE, PRODUCT_ID, NULL, SALES_AMOUNT
FROM OFFLINE_SALE
WHERE YEAR(SALES_DATE)=2022 AND MONTH(SALES_DATE)=3
)
ORDER BY SALES_DATE, PRODUCT_ID, USER_ID
UNION -> 중복된 row는 제거
UNION ALL -> 중복제거 x
WHERE SALES_DATE LIKE '2022-03%'
로 WHERE문 대체 가능
NULL AS USER_ID 로 작성해서 칼럼명 명시 가능
SELECT NAME, DATETIME
FROM ANIMAL_INS
ORDER BY ANIMAL_ID DESC
SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS
WHERE INTAKE_CONDITION='Sick'
ORDER BY ANIMAL_ID
SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS
WHERE INTAKE_CONDITION != 'Aged'
ORDER BY ANIMAL_ID
SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS
ORDER BY ANIMAL_ID
SELECT ANIMAL_ID, NAME, DATETIME
FROM ANIMAL_INS
ORDER BY NAME, DATETIME DESC
SELECT NAME
FROM ANIMAL_INS
ORDER BY DATETIME
LIMIT 1
LIMIT N -> 상위 N개 조회
SELECT COUNT(*) AS USERS
FROM USER_INFO
WHERE YEAR(JOINED)=2021 AND (AGE BETWEEN 20 AND 29)
SELECT ITEM_ID, ITEM_NAME, RARITY
FROM ITEM_INFO
WHERE ITEM_ID IN ( -- 업그레이드 되는 아이템 ID 목록
SELECT IT.ITEM_ID
FROM ITEM_INFO AS II
JOIN ITEM_TREE AS IT ON (II.ITEM_ID = IT.PARENT_ITEM_ID)
WHERE II.RARITY = 'RARE'
)
ORDER BY ITEM_ID DESC
SELECT ID, EMAIL, FIRST_NAME, LAST_NAME
FROM DEVELOPER_INFOS
WHERE SKILL_1 = 'Python' or SKILL_2 = 'Python' or SKILL_3 = 'Python'
ORDER BY ID ASC
다음과 같은 방법도 가능하다
SELECT ID, EMAIL, FIRST_NAME, LAST_NAME
FROM DEVELOPER_INFOS
WHERE "Python" IN (SKILL1,SKILL2,SKILL3)
ORDER BY ID ASC
SELECT ID, EMAIL, FIRST_NAME, LAST_NAME
FROM DEVELOPERS
WHERE ID IN ( -- 해당되는 개발자 ID 목록
SELECT DISTINCT DEV.ID
FROM SKILLCODES AS SK, DEVELOPERS AS DEV
WHERE (SK.CODE & DEV.SKILL_CODE) > 0 -- 비트 and 연산한 결과가 0보다 크면 포함
AND SK.NAME IN ('Python', 'C#')
)
ORDER BY ID ASC
lv2로 선정되어 있는게 의외다. 비트연산의 & 결과가 0보다 크면 공통된 비트 1이 존재한다는 점을 이용하면 된다.
이때, FROM에 테이블 2개를 연달아 적어서 모든 가능한 경우인 카테시안 곱(Cartesian Product)을 구했다.
SELECT *
FROM FOOD_PRODUCT
ORDER BY PRICE DESC
LIMIT 1
다음과 같이도 가능
SELECT *
FROM FOOD_PRODUCT
WHERE PRICE =
(SELECT MAX(PRICE) PRICE FROM FOOD_PRODUCT);
SELECT MAX(PRICE) AS MAX_PRICE
FROM PRODUCT
SELECT MAX(DATETIME)
FROM ANIMAL_INS
SELECT MIN(DATETIME)
FROM ANIMAL_INS
SELECT COUNT(*)
FROM ANIMAL_INS
SELECT COUNT(DISTINCT NAME)
FROM ANIMAL_INS
WHERE NAME IS NOT NULL
DISTINCT: 칼럼명 앞에 붙여서 unique하게 처리
SELECT SUM(PRICE) AS TOTAL_PRICE
FROM ITEM_INFO
WHERE RARITY='LEGEND'
SELECT FOOD_TYPE, REST_ID, REST_NAME, FAVORITES
FROM REST_INFO
WHERE (FOOD_TYPE, FAVORITES) IN (
SELECT FOOD_TYPE, MAX(FAVORITES)
FROM REST_INFO
GROUP BY FOOD_TYPE
)
ORDER BY FOOD_TYPE DESC
처음에 못풀었다. 서브쿼리 + WHERE IN을 이용하는 방법으로 풀이가 가능하다.
SELECT USER_ID, NICKNAME, SUM(PRICE) AS TOTAL_SALES
FROM USED_GOODS_USER
INNER JOIN USED_GOODS_BOARD ON (WRITER_ID=USER_ID)
WHERE STATUS='DONE'
GROUP BY USER_ID
HAVING TOTAL_SALES >= 700000
ORDER BY TOTAL_SALES
SQL 수행 순서 알아야
FROM+JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT
SELECT AUTHOR_ID, AUTHOR_NAME, CATEGORY, SUM(PRICE*SALES) AS TOTAL_SALES
FROM BOOK
INNER JOIN AUTHOR USING (AUTHOR_ID)
INNER JOIN BOOK_SALES USING (BOOK_ID)
WHERE YEAR(sales_date)=2022 AND MONTH(sales_date)=1
GROUP BY AUTHOR_ID, CATEGORY
ORDER BY AUTHOR_ID, CATEGORY DESC
SUM(PRICE*SALES)와 같이 칼럼끼리의 곱 SUM한 결과 작성
SELECT CATEGORY, SUM(SALES) AS TOTAL_SALES
FROM BOOK
INNER JOIN BOOK_SALES USING (BOOK_ID)
WHERE YEAR(SALES_DATE)=2022 AND MONTH(SALES_DATE)=1
GROUP BY CATEGORY
ORDER BY CATEGORY
SELECT
CAR_ID,
CASE
WHEN CAR_ID IN (
SELECT DISTINCT CAR_ID -- 대여중인 CAR_ID 목록
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE <= '2022-10-16' AND '2022-10-16' <= END_DATE
)
THEN '대여중'
ELSE '대여 가능'
END AS AVAILABILITY
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
GROUP BY CAR_ID
ORDER BY CAR_ID DESC
CASE 사용하기
CASE
WHEN 조건
THEN '반환 값'
WHEN 조건
THEN '반환 값'
ELSE 'WHEN 조건에 해당 안되는 경우 반환 값'
END
SELECT MCDP_CD AS '진료과코드', COUNT(*) AS '5월예약건수'
FROM APPOINTMENT
WHERE YEAR(APNT_YMD)=2022 AND MONTH(APNT_YMD)=5
GROUP BY MCDP_CD
ORDER BY 5월예약건수, 진료과코드
ORDER BY '5월예약건수', '진료과코드' 로 작성하면 틀린다.
별칭으로 인식하지 않고, 별칭을 로직에서 사용할 때는 백틱으로 별칭을 묶어주어야 한다.
또는 ORDER BY 2,1 과 같이 작성해서 순서로 정렬도 가능하다.
SELECT CAR_TYPE, COUNT(*) AS CARS
FROM CAR_RENTAL_COMPANY_CAR
WHERE (OPTIONS LIKE '%통풍시트%') OR (OPTIONS LIKE '%열선시트%') OR (OPTIONS LIKE '%가죽시트%')
GROUP BY CAR_TYPE
ORDER BY CAR_TYPE
LIKE 사용해서 포함 유무 파악하기
SELECT MONTH(START_DATE) AS MONTH, CAR_ID, COUNT(*) AS RECORDS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE CAR_ID IN (
SELECT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE YEAR(START_DATE) = 2022 AND MONTH(START_DATE) IN (8,9,10)
GROUP BY CAR_ID
HAVING COUNT(*)>=5 -- 이상이니까 등호붙여야
) AND YEAR(START_DATE) = 2022 AND MONTH(START_DATE) IN (8,9,10)
GROUP BY MONTH(START_DATE), CAR_ID
ORDER BY MONTH ASC, CAR_ID DESC
SELECT INGREDIENT_TYPE, SUM(TOTAL_ORDER) AS TOTAL_ORDER
FROM FIRST_HALF AS FH
JOIN ICECREAM_INFO AS II ON (FH.FLAVOR = II.FLAVOR)
GROUP BY INGREDIENT_TYPE
ORDER BY TOTAL_ORDER
SELECT CATEGORY, PRICE AS MAX_PRICE, PRODUCT_NAME
FROM FOOD_PRODUCT
WHERE (CATEGORY, PRICE) IN (
SELECT CATEGORY, MAX(PRICE)
FROM FOOD_PRODUCT
WHERE CATEGORY IN ('과자', '국', '김치', '식용유')
GROUP BY CATEGORY
)
ORDER BY MAX_PRICE DESC
WHERE (A,B) IN (서브쿼리) 이용하기
SELECT ANIMAL_TYPE, COUNT(*) AS count
FROM ANIMAL_INS
WHERE ANIMAL_TYPE IN ('Cat', 'Dog')
GROUP BY ANIMAL_TYPE
ORDER BY ANIMAL_TYPE ASC
SELECT NAME, COUNT(*) AS COUNT
FROM ANIMAL_INS
WHERE NAME IS NOT NULL
GROUP BY NAME
HAVING COUNT(*) >=2
ORDER BY NAME
이름이 없는 동물은 집계에서 제외이므로 IS NOT NULL 빼먹으면 안된다.
제외해주세요 -> 처음부터 제외시키자.
SELECT YEAR(SALES_DATE) AS YEAR, MONTH(SALES_DATE) AS MONTH, GENDER, COUNT(DISTINCT OS.USER_ID) AS USERS
FROM ONLINE_SALE AS OS
INNER JOIN USER_INFO AS UI ON (OS.USER_ID=UI.USER_ID)
WHERE GENDER IS NOT NULL
GROUP BY YEAR(SALES_DATE), MONTH(SALES_DATE), GENDER
ORDER BY YEAR, MONTH, GENDER
성별 정보가 없는 경우 결과에서 제외 -> WHERE에서 처음부터 제거.
회원수를 구하므로 중복이 있을 수 있다. -> COUNT(DISTINCT OS.USER_ID)) 로 구하기