프로그래머스 SQL 고득점 Kit으로 공부한 내용들을 정리합니다.
SUM, MAX, MIN
COUNT
데이터의 개수를 셀 때 사용한다.
SELECT COUNT(NAME) FROM table;
중복되지 않은 이름 데이터 개수를 센다면 DISTINCT를 사용한다.
SELECT COUNT(DISTINCT NAME) FROM table;
문제 풀이
SELECT * FROM FOOD_PRODUCT WHERE PRICE = (SELECT MAX(PRICE) PRICE FROM FOOD_PRODUCT);
SELECT * FROM FOOD_PRODUCT WHERE PRICE = (SELECT MAX(PRICE) PRICE FROM FOOD_PRODUCT);
- SELECT * FROM FOOD_PRODUCT : FOOD_PRODUCT 테이블에서 모든 컬럼(*)을 선택해라
- WHERE PRICE = ( … ) : 가격이 ( … )인 레코드만
- (SELECT MAX(PRICE) PRICE FROM FOOD_PRODUCT : FOOD_PRODUCT의 PRICE 컬럼 데이터 중에서 가장 큰 것으로
SELECT PRICE MAX_PRICE FROM PRODUCT WHERE PRICE = (SELECT MAX(PRICE) PRICE FROM PRODUCT);
SELECT PRICE MAX_PRICE FROM PRODUCT WHERE PRICE = (SELECT MAX(PRICE) PRICE FROM PRODUCT);
- SELECT PRICE MAX_PRICE FROM PRODUCT : PRODUCT 테이블에서 선택한 PRICE 컬럼명을 MAX_PRICE라고 지정해라.
- WHERE PRICE = (SELECT MAX(PRICE) PRICE FROM PRODUCT) : 가격이 PRODUCT 테이블에서 PRICE 컬럼 데이터 중에서 가장 큰 것으로
SELECT COUNT(*) from ANIMAL_INS;
SELECT COUNT(*) from ANIMAL_INS; : ANIMAL_INS 테이블에서 NULL 포함 여부에 관계없이 대상 테이블의 모든 행을 카운트해라.
SELECT COUNT(DISTINCT NAME)
FROM ANIMAL_INS
WHERE NAME IS NOT NULL
- 동물의 이름 갯수를 조회하므로, NAME에 COUNT 함수를 적용한다.
- 컬럼 내 같은 데이터가 존재하면, 중복제거하기 위해 DISTINCT를 사용한다.
- NAME이 NULL 값이 아닌 것을 비교한다.
SELECT
LIKE, NOT LIKE
특정 문자를 포함하는지 데이터를 확인하고 있다면, 제외하고 출력한다.
- LIKE: 특정 문자를 포함하는 데이터를 출력한다.
- NOT LIKE: 특정 문자를 포함하지 않는 데이터를 출력한다.
- 컬럼 LIKE '문자%': 특정 문자로 시작하는 데이터 확인
- 컬럼 LIKE '%문자': 특정 문자로 종료하는 데이터 확인
- 컬럼 LIKE '%문자%': 문자 시작 ~ 종료까지 특정 문자 포함 여부 확인
JOIN(INNER JOIN, OUTER JOIN, LEFT JOIN, RIGHT JOIN)
- JOIN: 교집합
- LEFT JOIN: 왼쪽 테이블을 중심으로 오른쪽의 테이블을 매치시킨다.
- 왼쪽 테이블의 한 개의 레코드에 여러개의 오른쪽 테이블 레코드가 일치할 경우, 해당 왼쪽 레코드를 여러번 표시한다.
- 왼쪽은 무조건 표시하고, 매치되는 레코드가 오른쪽에 없으면 NULL을 표시한다.
- RIGHT JOIN: 오른쪽 테이블을 중심으로 왼쪽 테이블을 매치시킨다.(LEFT JOIN과 방향만 오른쪽으로 바뀐 것!)
- 오른쪽 테이블의 한 개의 레코드에 여러 개의 왼쪽 테이블 레코드가 일치할 경우, 해당 오른쪽 레코드를 여러번 표시한다.
- 오른쪽은 무조건 표시하고, 매치되는 레코드가 왼쪽에 없으면 NULL을 표시한다.
- OUTER JOIN: 조건에 부합하지 않은 행까지 포함시켜 결합한다.
- FULL JOIN -> 거의 사용할 일이 없으며, 지원하지 않은 경우도 있다.
DATE_FORMAT
DATE_FORMAT(날짜, 형식): 날짜를 지정한 형식으로 출력한다.
SELECT DATE_FORMAT(NOW(),'%Y-%m-%d') AS DATE
출력 결과: 2023-09-21
쿼리문 합치는 방법(UNION, UNION ALL)
UNION: 여러 개의 쿼리문을 사용하여 하나의 데이터로 출력한다.
UNION ALL: 각각에 쿼리에 나온 데이터를 하나로 합쳐준다. 이때 중복되는 갓도 그대로 출력된다.
문제 풀이
SELECT NAME, COUNT(NAME) AS 'COUNT'
FROM ANIMAL_INS
GROUP BY NAME
HAVING COUNT(NAME) > 1
ORDER BY NAME ASC
- NAME 필드로 그룹화한다.
- COUNT(NAME)으로 개수를 세었을 때, 1보다 큰 원소들을
- NAME으로 오름차순 정렬한다.
SELECT ANIMAL_ID, NAME FROM ANIMAL_INS
WHERE INTAKE_CONDITION NOT LIKE 'Aged'
ORDER BY ANIMAL_ID
- NOT LIKE: INTAKE_CONDITION 필드가 'Aged' 라는 문자를 포함하지 않은 것들을 선택한다.
SELECT ANIMAL_ID, NAME, DATETIME FROM ANIMAL_INS
ORDER BY NAME, DATETIME DESC
- NAME 기준으로 오름차순 정렬하고
- DATETIME 기준으로 내림차순 정렬한다.
-> 여러 기준으로 정렬할 때는 `,`로 구분한다.
SELECT F.FLAVOR
FROM FIRST_HALF F JOIN ICECREAM_INFO I ON F.FLAVOR = I.FLAVOR
WHERE (F.TOTAL_ORDER > 3000) AND (I.INGREDIENT_TYPE = "fruit_based")
ORDER BY F.TOTAL_ORDER DESC
- 각각의 컬럼에 해당하는 조건을 출력하기 위해 두 테이블을 연결한다.
- (INNER) JOIN으로 조건에 해당하는 행을 추출한다.
- 상반기 아이스크림 총 주문량이 3000보다 높은 아이스크림과 아이스크림의 주 성분이 과일인 아이스크림을 찾는다.
- WHERE 절을 이용하고 AND로 묶어준다.
- 총 주문량이 큰 순서대로 조회한다.
- ORDER BY 총 주문량 DESC
WHERE 사용한 경우
SELECT A.REST_ID, B.REST_NAME, B.FOOD_TYPE, B.FAVORITES, B.ADDRESS, ROUND(AVG(A.REVIEW_SCORE),2) AS SCORE
FROM REST_REVIEW A
JOIN REST_INFO B ON A.REST_ID = B.REST_ID
WHERE B.ADDRESS LIKE '서울%'
GROUP BY A.REST_ID
ORDER BY SCORE DESC, B.FAVORITES DESC;
HAVING 사용한 경우
SELECT A.REST_ID, B.REST_NAME, B.FOOD_TYPE, B.FAVORITES, B.ADDRESS, ROUND(AVG(A.REVIEW_SCORE),2) AS SCORE
FROM REST_REVIEW A
JOIN REST_INFO B ON A.REST_ID = B.REST_ID
GROUP BY A.REST_ID
HAVING B.ADDRESS LIKE '서울%'
ORDER BY SCORE DESC, B.FAVORITES DESC;
👉 더 읽어보면 좋을 내용: SQL Having과 Where 차이
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
- USER_ID와 PRODUCT_ID로 그룹화한다.
- GROUP BY 사용
- 그룹화한 결과를 COUNT 함수로 합계를 구했을 때, 개수가 2 이상인 것들이 재구매 상품이 된다.
- HAVING, COUNT 함수 사용
-- 온라인
(SELECT DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DATE, PRODUCT_ID, USER_ID, SALES_AMOUNT
FROM ONLINE_SALE
WHERE SALES_DATE LIKE '2022-03-%'
UNION
-- 오프라인
SELECT DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DATE, PRODUCT_ID, NULL AS USER_ID, SALES_AMOUNT
FROM OFFLINE_SALE
WHERE SALES_DATE LIKE '2022-03-%')
ORDER BY SALES_DATE, PRODUCT_ID, USER_ID
온라인 쿼리문 작성, 오프라인 쿼리문 작성, UNION으로 합친다.
NULL
문제 풀이
SELECT WAREHOUSE_ID, WAREHOUSE_NAME, ADDRESS, IF(FREEZER_YN is NULL, 'N', FREEZER_YN) AS FREEZER_YN
FROM FOOD_WAREHOUSE
WHERE ADDRESS LIKE '경기도 %'
IF문을 사용하여, FREEZER_YN이 NULL이면, 'N'을 출력하도록 한다.
GROUP BY
GROUP BY
유형별로 갯수를 가져오고 싶을 때, 컬럼에 데이터를 그룹화하는 방법
- GROUP BY: 특정 컬럼을 그룹화한다.
- HAVING: 특정 컬럼을 그룹화한 결과에 조건을 건다.
=> WHERE랑 HAVING의 차이는 WHERE는 그룹화하기 전이고, HAVING은 그룹화 후에 조건이다.
컬럼 그룹화
SELECT 컬럼 FROM 테이블 GROUP BY 그룹화할 컬럼;
조건 처리 후에 컬럼 그룹화
SELECT 컬럼 FROM 테이블 WHERE 조건식 GROUP BY 그룹화할 컬럼;
컬럼 그룹화 후에 조건 처리
SELECT 컬럼 FROM 테이블 GROUP BY 그룹화할 컬럼 HAVING 조건식;
조건 처리 후에 컬럼 그룹화 후에 조건 처리
SELECT 컬럼 FROM 테이블 WHERE 조건식 GROUP BY 그룹화할 컬럼 HAVING 조건식;
SUM VS COUNT
- SUM: 요소들의 value들의 합
- COUNT: 요소들의 개수
BETWEEN VS IN VS LIKE
BETWEEN 연산자
select * from scores where num between 30 and 40;
- scores 테이블의 num 컬럼 값이 30에서 40 사이인 데이터를 출력한다.
- AND 연산자를 이용하여 쓸 수도 있다.
- num >= 30 and num <= 40
IN 연산자
select * from scores where num in (30, 40, 50, 60);
- scores 테이블의 num 컬럼 값이 30 또는 40 또는 50 또는 60인 데이터를 출력한다.
- in 연산자는 괄호 안의 여러 개의 값 중, 하나라도 일치하면 참을 반환한다.
- OR 연산자를 이용하여 쓸 수도 있다.
- where num = 30 or num = 40 or num = 50 or num = 60
LIKE 연산자
select * from scores where subject like '%수학%';
scores 테이블의 subject 컬럼의 값 중에 '수학'이 포함되는 데이터를 출력한다.
- '%수학%': 수학이 포함되는 데이터를 출력한다.
- '수학%': 수학으로 시작되는 데이터를 출력한다.
- '%수학': 수학으로 끝나는 데이터를 출력한다.
select * from scores where subject = '수학';
like와 달리 = 을 사용할 경우, 정확히 일치되는 데이터만 출력한다.
CASE문 사용
CASE
WHEN 조건1 THEN 결과값1
WHEN 조건2 THEN 결과값2
WHEN 조건N THEN 결과값N
ELSE 결과값
END
- 조건을 통과하고 첫 번째 조건이 충족되면 값을 반환한다.
- 조건에 따라 True(참)이면 읽기를 중지하고 결과를 반환하고
- 조건이 True(참)가 아니면 ELSE 절의 값을 반환한다.
- ELSE 부분이 없고 조건이 참이 아니면 NULL을 반환한다.
TRUNCATE(숫자, 버릴 자릿수)
SELECT TRUNCATE(1234.56789, 1) // 1234.5
SELECT TRUNCATE(1234.56789, 4) // 1234.5678
SELECT TRUNCATE(1234.56789, -1) // 1230
SELECT TRUNCATE(1234.56789, -2) // 1200
문제 풀이
SELECT INGREDIENT_TYPE, SUM(TOTAL_ORDER) AS TOTAL_ORDER
FROM FIRST_HALF F
INNER JOIN ICECREAM_INFO I
ON F.FLAVOR = I.FLAVOR
GROUP BY INGREDIENT_TYPE
ORDER BY TOTAL_ORDER;
- 아이스크림의 주 성분, 성분별 총 주문량을 조회한다.
- 아이스크림 맛을 기준으로 테이블을 병합한다.
- 아이스크림의 주 성분으로 묶는다.
- 총 주문량을 기준으로 오름차순 정렬한다.
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 (FOOD_TYPE, FAVORITES) IN (SELECT FOOD_TYPE, MAX(FAVORITES)
FROM REST_INFO
GROUP BY FOOD_TYPE)
- 서브쿼리문을 사용하여 FOOD_TYPE으로 그룹화한 것에서 가장 FAVORITES가 높은 값을 불러온다.
- 이 데이터를 원본 테이블과 매칭시킨다.
- 매칭 결과를 FOOD_TYPE으로 정렬하여 출력한다.
SELECT USER_ID, NICKNAME, SUM(PRICE) AS TOTAL_SALES
FROM USED_GOODS_BOARD, USED_GOODS_USER
WHERE WRITER_ID = USER_ID AND STATUS = "DONE"
GROUP BY USER_ID
HAVING SUM(PRICE) >= 700000
ORDER BY TOTAL_SALES ASC
- USER_ID, NICKNAME, 가격 합계를 TOTAL_SALES라고 이름 붙여서 불려온다.
- USED_GOODS_BOARD와 USED_GOODS_USER 테이블에서
- WRITER_ID와 USER_ID가 같고, STATUS가 "DONE"인 것을
- USER_ID로 묶는다.
- PRICE의 합이 700000 이상인 것들을
- TOTAL_SALES 기준으로 오름차순 정렬한다.
SELECT A.AUTHOR_ID, B.AUTHOR_NAME, A.CATEGORY, SUM(A.PRICE * C.SALES) AS TOTAL_SALES
FROM BOOK AS A
JOIN AUTHOR AS B ON A.AUTHOR_ID = B.AUTHOR_ID
JOIN BOOK_SALES AS C ON A.BOOK_ID = C.BOOK_ID
WHERE C.SALES_DATE LIKE '2022-01%'
GROUP BY A.AUTHOR_ID, A.CATEGORY
ORDER BY A.AUTHOR_ID, A.CATEGORY DESC
- AUTHOR_ID, AUTHOR_NAME, CATEGORY, PRICE * SALES들의 합을 불러온다.
- 여러개의 테이블을 조인한다.
- BOOK 테이블과 AUTHOR 테이블을 각 테이블의 AUTHOR_ID로 조인한다.
- 위에서 조인한 결과와 BOOK_SALES 테이블을 각 테이블의 BOOK_ID로 조인한다.
- SALES_DATE의 형식으로 2022년 1월에 해당하는 데이터인지 확인한다.
- AUTHOR_ID와 CATEGORY로 각각 그룹화한다.
- AUTHOR_ID와 CATEGORY로 내림차순 정렬한다.
SELECT MONTH(START_DATE) AS MONTH, CAR_ID, COUNT(*) AS RECORDS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE DATE_FORMAT(START_DATE, '%Y-%m') BETWEEN '2022-08' AND '2022-10'
AND CAR_ID IN (SELECT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE DATE_FORMAT(START_DATE, '%Y-%m') BETWEEN '2022-08' AND '2022-10'
GROUP BY CAR_ID
HAVING COUNT(*) >= 5)
GROUP BY CAR_ID, MONTH(START_DATE)
HAVING RECORDS >= 1
ORDER BY MONTH, CAR_ID DESC
- 대여 시작일을 기준으로 2022년 8월부터 2022년 10월까지 총 대여 횟수가 5회 이상인 자동차
- 서브 쿼리로 구한다.
- DATE_FORMAT 함수를 이용해 차종별 대여 시작일이 2022년 8월과 10월 사이에 있는 행들을 구한다.
- HAVING 절을 사용해 차종별 총 대여 횟수가 5번 이상인지 확인한다.
- 해당 기간 동안의 월별 자동차 ID 별 총 대여 횟수 리스트를 출력한다.
- 대여 시작일이 2022년 8월부터 2022년 10월인 자동차 중에서 구한 서브쿼리에 해당하는 행을 구한다.
- GROUP BY를 CAR_ID별, 월별로 총 대여 횟수를 구한다.
- 특정 월의 총 대여횟수가 0인 경우는 결과에서 제외하기 위해 HAVING 절에 대여 횟수가 1 이상인 경우를 조건으로 걸어준다.
- 정렬한다.
- ORDER BY: MONTH 오름차순(ASC 생략가능) 정렬한다. CAR_ID는 내림차순(DESC)로 정렬한다.
SELECT A.CATEGORY, SUM(B.SALES) AS TOTAL_SALES
FROM BOOK AS A
JOIN BOOK_SALES AS B ON A.BOOK_ID = B.BOOK_ID
WHERE SALES_DATE LIKE '2022-01-%'
GROUP BY A.CATEGORY
ORDER BY A.CATEGORY
SELECT CATEGORY, PRICE AS MAX_PRICE, PRODUCT_NAME
FROM FOOD_PRODUCT
WHERE PRICE IN (
SELECT MAX(PRICE)
FROM FOOD_PRODUCT
GROUP BY CATEGORY)
AND CATEGORY IN ('과자', '국', '김치', '식용유')
ORDER BY MAX_PRICE DESC
MAX로 PRICE를 조회하면 해당 컬럼에 대한 값만 나오고, 행 전체는 나오지 않는다. 서브 쿼리로 걸러서 먼저 조회한다.
- SELECT MAX(PRICE) FROM FOOD_PRODUCT GROUP BY CATEGORY
=> 서브 쿼리로 카테고리별 가격이 가장 비싼 식품을 조회한다. - WHERE PRICE IN (서브쿼리) AND CATEGORY IN ('과자', '국', '김치', '식용유')
=> PRICE가 서브쿼리에서 조회한 내역안에 있고, CATEGORY가 '과자', '국', '김치', '식용유' 중 하나라면 조회한다. - 결과는 MAX_PRICE로 정렬한다.
자동차 대여 기록에서 대여중 / 대여 가능 여부 구분하기
SELECT CAR_ID,
CASE
WHEN CAR_ID IN (SELECT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE '2022-10-16' BETWEEN START_DATE AND END_DATE) THEN '대여중'
ELSE '대여 가능'
END 'AVAILABILITY'
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
GROUP BY CAR_ID
ORDER BY CAR_ID DESC
- 2022년 10월 16일에 대여 중인 자동차인 경우 '대여중'이라고 표시하고, 대여중이지 않은 자동차인 경우 '대여 가능'을 표시하는 컬럼(컬럼명: AVAILABILITY)을 추가한다.
- CASE문을 사용한다.
- 서브 쿼리를 사용해 CAR_ID를 구한다.
- GROUP BY를 통해 CAR_ID 별로 위에서 구한 조건에 해당하는 CAR_ID라면 '대여중'으로, 아니면 '대여 가능'으로 표시해준다. AVAILABILITY라는 컬럼에 값을 출력한다.
- 자동차 ID를 기준으로 내림차순 정렬한다.
- ORDER BY 컬럼명 DESC
SELECT YEAR(SALES_DATE) AS YEAR, MONTH(SALES_DATE) AS MONTH, GENDER, COUNT(DISTINCT A.USER_ID) AS USERS
FROM USER_INFO A
JOIN ONLINE_SALE B ON A.USER_ID = B.USER_ID
GROUP BY YEAR, MONTH, GENDER
HAVING GENDER IS NOT NULL
ORDER BY YEAR, MONTH, GENDER
1. 년, 월, 성별 별로 상품을 구매한 회원수를 집계하는 SQL문을 작성한다.
- 년 → YEAR(SALES_DATE)
- 월 → MONTH(SALES_DATE)
- 성별 → GENDER
- 상품을 구매한 회원 수 → COUNT(DISTINCT USER_ID)
→ 동일한 날짜, 회원 ID, 상품 ID 조합에 대해서는 하나의 판매 데이터만 존재한다.
→ 중복제거를 위해 DISTINCT 키워드를 사용한다.
2. 3. USER_INFO 테이블을 A로 지정하고, ONLINE_SALE 테이블을 B로 지정해 조인한다.
4. 성별 정보가 없는 경우 결과에서 제외한다. IS NOT NULL 을 이용하여 제거한다.
5. 년, 월, 성별 별로 상품을 구매한 회원수를 집계한다.
6. 년, 월, 성별을 기준으로 오름차순 정렬한다.
SET @HOUR := -1;
SELECT (@HOUR := @HOUR + 1) AS HOUR,
(SELECT COUNT(*) FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = @HOUR) AS COUNT
FROM ANIMAL_OUTS
WHERE @HOUR < 23
- SET 함수를 사용해 0~23까지의 테이블을 완성한다.
- @HOUR := -1 ➡️ HOUR 변수에 -1을 대입한다.
- HOUR 변수에 HOUR + 1을 대입한다.
- WHERE문을 이용해 @HOUR가 0부터 23까지로 테이블 범위를 정한다.
- SELECT문으로 HOUR 변수와 시간이 같을 때를 COUNT 하기 때문에 WHERE문으로 HOUR(DATETIME) = @HOUR와 같아질 때를 찾는다.
SELECT TRUNCATE(PRICE, -4) AS PRICE_GROUP, COUNT(PRODUCT_ID) AS PRODUCTS
FROM PRODUCT
GROUP BY PRICE_GROUP
ORDER BY PRICE_GROUP
TRUNCATE 함수를 사용해서, 만의 자리만 제외하고 남은 값들을 절사한다.