[MySQL]LV 4 문제 모음
Intro
SQL 문제들은 많이 없기 때문에 LV 1 ~ LV 5
까지 문제들을 레벨별로 모아서 한번에 정리하겠다.
문제 1 - with 심화, right join
-
with, union, union all, recursive, right join
이렇게 다양한 함수들을 많이 사용한 문제-
with
: CTE(common table expression)을 생성하는 문법WITH [RECURSIVE] TABLE명 AS ( SELECT - # 비반복문. 무조건 필수 [UNION ALL] # RECURSIVE 사용 시 필수. 다음에 이어붙어야 할 때 사용 SELECT - [WHERE -] # RECURSIVE 사용 시 필수. 정지 조건 필요할 때 사용 )
-
union
은 결합하며 중복제거,union all
은 결합하며 중복제거X -
right join
은 오른쪽 테이블의 모든 값을 가져와서 남는게 없도록 함
-
코드
-- 코드를 입력하세요
# 테이블을 만들어서 0~23을 추가한다. 그리고 right join 을 활용하겠다.
with recursive newTable as (
select 0 as hour
union all
select hour+1 from newTable
where hour < 23
)
select b.hour, count(a.animal_id) as count
from animal_outs a right join newTable b on (hour(a.datetime)=b.hour)
group by hour order by hour;
풀이
with recursive cts as (...)
코드로 임시로 테이블을 만든다.
- 테이블을 만들어서 0~23 을 추가하고, 이것을 join 에 사용하게 된다.
select b.hour, count(a.animal_id) as count
from animal_outs a right join newTable b on (hour(a.datetime)=b.hour)
- right join을 활용해서 newTable의 hour(0~23)을 살린다.
count(*)
로 안하고 count(a.animal_id)
로 하는이유는??
-
count(*)
로 하면 모든 부분을 따지므로b.hour
부분도 카운트가 되어서 최소 1개 이상이 되기 때문이다. - 따라서 a쪽 테이블만 개수 세줘야 정상 출력 된다.
문제 2 - 서브쿼리, limit, order by에 집계함수
코드
select a.member_name, b.review_text, date_format(b.review_date,'%Y-%m-%d') as review_date from member_profile a join rest_review b on (a.member_id=b.member_id)
where (a.member_id) = (select a.member_id from member_profile a join rest_review b on (a.member_id=b.member_id)
group by a.member_id order by count(*) desc limit 1)
order by b.review_date, b.review_text;
-- 최상위 값 구하면 되는거라 limit 1 -> 처음에 max함수만 생각해서 못 풀었다.
# select a.member_id, count(*) as count from member_profile a join rest_review b on (a.member_id=b.member_id)
# group by a.member_id order by count desc limit 1;
풀이
문제에서 리뷰를 가장 많이 작성한 회원의 리뷰들을 조회하는 SQL을 구하라고 하였다.
서브쿼리를 통해서 해당 회원을 구했는데, 이때 max 함수가 아니라 limit 으로 구하면 된다.
order by 에는 집계함수도 올 수 있다. 따라서 그룹별 count(*)
로 정렬을 했다.
- 또는 인라인뷰에 윈도우함수로 랭크 매기고, 메인에서 1등 랭크인지 필터링 하면된다.
문제 3 - count(distinct a.user_id)
코드
-- 회원 user_info, 상품판매 online_sale
-- count(distinct a.user_id) 가 핵심
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;
풀이
위 테이블은 join하면 id부분이 중복이 발생한다.
이를 제거하기 위해 distinct를 사용!
- count 함수 안에서 사용했다는게 특징
-
이 방식은 각 회원이 특정 기간에 여러 번 구매하더라도, 그 회원은 한 번만 카운트 되도록 하기 위함
- 문제의 이러한 요구사항 때문에 중복 제거를 사용하게 됨.
- 1:N 관계이므로 전체 행이 중복일 수는 없음. N:N이면 모를까. 그래도 distinct를 쓴 이유가 위의 이유임.
문제 4 - union all
코드
-- hint : union all
-- 문제를 읽어보면 join을 생각하는게 아니라 합집합 형태가 필요!
select date_format(a.sales_date,'%Y-%m-%d') as sales_date, a.product_id, a.user_id, a.sales_amount
from
(select sales_date, product_id, user_id, sales_amount from online_sale where year(sales_date)=2022 and month(sales_date)=3
union all
select sales_date, product_id, null, sales_amount from offline_sale where year(sales_date)=2022 and month(sales_date)=3
) a
order by a.sales_date, a.product_id, a.user_id;
풀이
ONLINE_SALE
테이블과 OFFLINE_SALE
테이블에서 2022년 3월의 오프라인/온라인 상품 판매 데이터의 판매 날짜, 상품ID, 유저ID, 판매량을 출력하는 SQL문을 작성하라고 했다.
- 두 테이블의 합집합을 구해서 출력하면 되는 문제이다.
-
OFFLINE_SALE
에는user_id
컬럼이 없으므로 null로 출력해서 구한다. (문제에서도 null로 표기하라 했음)
문제 5 - timestamp<->date 차이
코드
-- timestamp 타입은 date 타입이랑 다르므로 date로 바꿔서 비교 필수
select a.apnt_no, b.pt_name, b.pt_no, a.mcdp_cd, c.dr_name, a.apnt_ymd from appointment a join patient b on (a.pt_no=b.pt_no) join doctor c on (a.mddr_id=c.dr_id)
where a.mcdp_cd='cs' and a.apnt_cncl_yn='N' and date(a.apnt_ymd) = '2022-04-13'
order by a.apnt_ymd;
풀이
join3개를 했고, date() 함수를 사용했다.
컬럼이 테이블 전부 사용하다 보니(출력) 중복 걱정도 없다.
문제 6 - join, group by, order by 심화
코드
-- first_half 와 july 테이블은 1:N으로 july 쪽은 sum함수로 구해줘야 한다.
-- 이를 위해 group by 를 활용
select b.flavor from first_half a join july b on (a.flavor=b.flavor)
group by b.flavor
order by (sum(b.total_order)+a.total_order) desc
limit 3;
풀이
7월 아이스크림 총 주문량과 상반기의 아이스크림 총 주문량을 더한 값이 큰 순서대로 상위 3개의 맛을 조회하는 SQL 문을 작성하는 문제이다.
- 7월 아이스크림 총 주문량은 july 테이블이며 그룹핑을 사용해서 sum 함수를 써야 구해진다.
- 상반기의 아이스크림 총 주문량은 first_half 테이블이며 바로 해당 컬럼을 사용하면 된다.
- 이 둘을 더해서 정렬해야 상위 3개의 맛을 조회할 수 있다.
- order by 가 이런 집계함수 뿐만아니라 연산하는 컬럼도 적용된다.
문제 7 - with(CTE), 스칼라 서브쿼리
코드
with use_date as (
select history_id, car_id, (datediff(end_date,start_date))+1 as using_date
from car_rental_company_rental_history
)
select b.history_id,
round((case
when b.using_date>=7 and b.using_date<30 then b.using_date*a.daily_fee*(1-(select discount_rate from car_rental_company_discount_plan where car_type=a.car_type and duration_type like '7%')*0.01)
when b.using_date>=30 and b.using_date<90 then b.using_date*a.daily_fee*(1-(select discount_rate from car_rental_company_discount_plan where car_type=a.car_type and duration_type like '30%')*0.01)
when b.using_date>=90 then b.using_date*a.daily_fee*(1-(select discount_rate from car_rental_company_discount_plan where car_type=a.car_type and duration_type like '90%')*0.01)
else b.using_date*a.daily_fee
end),0) as fee
from car_rental_company_car a
join use_date b on (b.car_id=a.car_id)
where a.car_type='트럭'
order by fee desc, b.history_id desc;
풀이
문제 : 대여 기록 별로 대여 금액(컬럼명: FEE) 를 구하자
- 대여 기록 별 (그룹 history_id) -> 굳이 그룹 안해도 풀림
- fee 를 구하기 위해 사용 날짜를 따로 with로 구해두자.(계산 편리)
- 마지막 plan 테이블은 case when 에서 할인율로 해결하자.
- case when 조건문의 경우 날짜로 한다는걸 생각.
- 참고) with절에서 SELECT *를 사용할 경우에는 중복된 컬럼명을 명시적으로 처리하지 않으므로 직접 명시를 해줘야 join으로 인한 중복 컬럼 에러를 마주치지 않을 수 있다.
문제 8 - group by, having
코드
with use_date as (
select history_id, car_id, start_date, end_date
from car_rental_company_rental_history
group by car_id having max(end_date)<'2022-11-01'
)
select a.car_id, a.car_type,
round((
30*a.daily_fee*(1-(select discount_rate from car_rental_company_discount_plan where duration_type like '30%' and car_type=a.car_type )*0.01)
),0) as fee
from car_rental_company_car a join use_date b on (a.car_id=b.car_id)
where a.car_type in ('세단', 'SUV')
group by a.car_id having fee >= 500000 and fee < 2000000
order by fee desc, a.car_type, a.car_id desc;
풀이
- history엔 동일한 car_id가 여러개 이므로 join 하면 중복으로 여러개 생기고, 대여 가능 여부 판단이 불가.
- 이를 해결하는 핵심코드 :
group by car_id having max(end_date)<'2022-11-01'
- 이를 해결하는 핵심코드 :
문제 9 - bit 비교 자세히, any 사용
코드
-- bit 비교? 1001 & 1000 -> 1000
-- 조인 결과 중복이 발생하므로 distinct 필요한 쿼리
-- 참고로 category가 front end 인 튜플이 여러개 임
select distinct a.id, a.email, a.first_name, a.last_name from developers a, skillcodes b
where a.skill_code & b.code = b.code and b.category='Front End'
order by a.id;
-- sum(CODE) 로 서브쿼리 만들어 비교하는 아이디어를 사용한 사람! 이런 풀이도 기억해두자.
-- 참고로 category가 front end 인 튜플이 여러개 임
select ID, EMAIL, FIRST_NAME, LAST_NAME
from DEVELOPERS
where SKILL_CODE&(select sum(CODE) from SKILLCODES where CATEGORY = 'Front End')
order by ID
-- 아니면 in? any? 연산자 사용해서 하는방식
-- 참고로 category가 front end 인 튜플이 여러개 임
0 < any (select skill_code & code
from skillcodes
where category = 'front end')
-- GRADE 구하기 -> with 3 -> sum(bit) 활용
with skill1 as (
select sum(b.code) as fecode from skillcodes b where b.category like 'F%'
), skill2 as (
select b.code as pycode from skillcodes b where b.name like 'Python'
), skill3 as (
select b.code as cscode from skillcodes b where b.name like 'C#'
), devs as (
select
(case
when a.skill_code&b.fecode and a.skill_code&c.pycode then 'A'
when a.skill_code&d.cscode then 'B'
when a.skill_code&b.fecode then 'C'
end
) as grade, a.id, a.email
from developers a, skill1 b, skill2 c, skill3 d
)
select grade, id, email
from devs
where grade is not null
order by grade, id;
-- 서브쿼리 활용 -> 별칭 사용 위해 having 에서 not null
select
(case
when skill_code & (select code from skillcodes where name = 'python') and
0 < any (select skill_code & code from skillcodes where category = 'front end') then 'A'
when skill_code & (select code from skillcodes where name = 'C#') then 'B'
when 0 < any (select skill_code & code from skillcodes where category = 'front end') then 'C'
end) as grade, id, email
from developers
group by grade, id, email
having grade is not null
order by grade asc, id asc;
-- 참고로 any -> exists 둘이 비슷하다~
when 0 < any (select skill_code & code from skillcodes where category = 'front end') then 'C'
when exists (select 1 from skillcodes where skill_code & code and category = 'front end') then 'C'
풀이
코드 참고~! 주석 참고~!
문제 10 - with(CTE) recursive
코드
-- self join
select c.id from ecoli_data a join ecoli_data b on (b.parent_id=a.id) join ecoli_data c on (c.parent_id=b.id)
where a.parent_id is null
order by c.id;
-- self join -> recursive 풀이
WITH RECURSIVE CTE
AS
(
SELECT ID, PARENT_ID, 1 AS DEPTH
FROM ECOLI_DATA
WHERE PARENT_ID IS NULL
UNION ALL
SELECT CHILD.ID, CHILD.PARENT_ID, CTE.DEPTH + 1
FROM ECOLI_DATA CHILD
INNER JOIN CTE
ON CHILD.PARENT_ID = CTE.ID
)
SELECT ID FROM CTE WHERE DEPTH = 3;
풀이
recursive 한 풀이와 아닌 풀이 둘 다 익혀두자.
특히 recursive 한 풀이의 의미를 이해하기 위해 아래 문법을 참고
with recursive test as (
-- non recursive
select 1 as hour
union all
-- recursive
select hour + 1
from test
-- terminate
where hour < 5
)
select * from rc;
-- 출력??
/*
1
2
3
4
*/
댓글남기기