데이터 분석/SQL

QCC 6회차

경 민 2025. 5. 26. 13:32

1번

  • 제출한 쿼리 (오답)
(select 
  gender,
  name,
  score
from students
where gender = 'F'
order by score desc limit 3)
union
(select 
  gender,
  name,
  score
from students
where gender = 'M'
order by score desc limit 3)

 

→ 틀린 부분 수정) age 조건을 안 넣어줬다..

(select 
  gender,
  name,
  score
from students
where gender = 'F'
order by score desc, age desc limit 3)
union
(select 
  gender,
  name,
  score
from students
where gender = 'M'
order by score desc, age desc limit 3)

⬇︎

  • 다른 방법으로 풀어본 쿼리
-- 성별 기준으로 점수 순위 부여한 CTE
with scoring as (
  select
    gender,
    name,
    score,
    rank() over(partition by gender order by score desc, age desc) rnk
  from students
  )

-- 상위 3명 필터링
select
    gender,
    name,
    score
from scoring
where rnk <= 3
order by gender, rnk

 

  • 튜터님 쿼리
with ranking as ( 
    select *,
      row_number() over (partition by gender order by score desc, age desc) rn  
    from students
)
select gender, name, score 
from ranking 
where rn <= 3
order by 1, rn

 

튜터님은 안전하게 row_number 사용하셨다.

** age 조건까지 order by 조건으로 넣어줬기 때문에 dense_rank 도 가능함


2번

  • 제출한 쿼리 (오답)
select
    due.title,
    due.a DUE,
    paid.b PAID
from
    (select 
      title,
      round(sum(line_total),0) a
    from books b 
    join book_order_items boi on b.id = boi.book_id
    join book_orders bo on boi.order_id = bo.id
    where bo.paid_date is null -- 미결제
    group by title) due
join
    (select 
      title,
      round(sum(line_total),0) b
    from books b 
    join book_order_items boi on b.id = boi.book_id
    join book_orders bo on boi.order_id = bo.id
    where bo.paid_date is not null -- 미결제
    group by title) paid
on due.title = paid.title
order by due.title

 

→ 틀린 부분 수정 1) DUE 또는 PAID 값이 NULL일 경우 0으로 처리하는 조건이 시험 중간에 추가됐다.. 공지를 못 보고 반영을 못함

→ 틀린 부분 수정 2) book 이랑 book_order_items 조인 할 때 LEFT JOIN 해줘야 한다. '모든 도서'에 대해 출력해야 하므로.

select
    due.title,
    ifnull(due.a,0) DUE,
    ifnull(paid.b,0) PAID
from
  (select 
    b.title,
    round(sum(boi.line_total),0) a
  from books b
  left join book_order_items boi on b.id = boi.book_id
  left join book_orders bo on boi.order_id = bo.id
  where bo.paid_date is null -- 미결제
  group by title) due
join
  (select 
    b.title,
    round(sum(boi.line_total),0) b
  from books b 
  left join book_order_items boi on b.id = boi.book_id
  left join book_orders bo on boi.order_id = bo.id
  where bo.paid_date is not null -- 미결제
  group by title) paid
on due.title = paid.title
order by due.title

 

→ 여전히 틀린 부분 ⭐️⭐️⭐️
중간에 JOIN하면 중복값만 조인되므로 전체 데이터를 구할 수 없다.

full outer join하면 되겠지만 Mysql에서는 지원 X

( left join과 right join을 각각 시켜준 다음 union 해야 함 )

 

비효율적이다.

⬇︎

  • 다른 방법으로 풀어본 쿼리
    • if문으로 컬럼을 각각 만들어 주면 된다.
select
  b.title,
  round(sum(coalesce(if(bo.paid_date is null, 1,0) * boi.line_total)),0) as DUE,
  round(sum(coalesce(if(bo.paid_date is not null, 1,0) * boi.line_total)),0) as PAID
from books b
left join book_order_items boi on b.id = boi.book_id
left join book_orders bo on boi.order_id = bo.id
group by b.title
order by b.title

 

  • 튜터님 쿼리
    • boolean은 1로 반환되는 거 활용해서 if문 생략
select b.title, 
  round(sum(coalesce((paid_date is null) * boi.line_total, 0)),0) as due, 
  round(sum(coalesce((paid_date is not null) * boi.line_total, 0)),0) as paid
from books b 
left join book_order_items boi on b.id = boi.book_id 
left join book_orders bo on boi.order_id = bo.id
group by 1
order by 1

 

‼️ coalesce 위치 차이

-- 1번
coalesce(round(sum((paid_date is null) * boi.line_total),0),0) as DUE

-- 2번
round(sum(coalesce((paid_date is null) * boi.line_total, 0)),0) as DUE,


어차피 null인 값은 sum에서 연산 제외되기 때문에 결국 결과값은 같다.
하지만 조건절이 추가되거나 복잡한 연산이 될 때 차이가 생길 수 있으므로
2번 코드처럼 먼저 null값을 처리한 후에 계산하는 게 안전하다.


3번

  • (해설 듣고) 다시 작성한 쿼리 
-- 1. 가장 먼저 구해야 하는 user별 cohort_month 계산하는 CTE 
with cohort as (
  select
    user_id,
    min(order_date) first_order_date,
    min(date_format(order_date,'%Y-%m')) first_order_month
  from orders
  group by user_id
  )

-- 2. cohort_month로부터 0~6개월 뒤에 몇 명 user 있는지 계산
select
  first_order_month,
  timestampdiff(month, first_order_date, order_date) months_after,
  count(distinct c.user_id) active_users
from orders o
join cohort c on o.user_id = c.user_id
where 
  first_order_month between '2023-01' and '2023-06'
  and timestampdiff(month, first_order_date, order_date) <= 6
group by 1,2
order by 1,2
  • 튜터님 쿼리
with cohort as (
  select user_id
    , min(order_date) first_order_date
    , min(date_format(order_date, '%Y-%m')) first_order_month 
  from orders
  group by 1
) 
select 
    c.first_order_month
    , timestampdiff(month, c.first_order_date, o.order_date) months_after
    , count(distinct o.user_id) active_user
from cohort c 
join orders o 
on c.user_id = o.user_id
where timestampdiff(month, c.first_order_date, o.order_date) between 0 and 6 
and c.first_order_month between '2023-01' and '2023-06'
group by 1, 2
order by 1, 2

 

timestampdiff(month, 기준일, 계산일)
→ 계산일이 기준일로부터 '몇 개월' 지났는지 반환해주는 함수

[ 단위 ] 
SECOND : 초
MINUTE : 분
HOUR : 시
DAY : 일
WEEK : 주
MONTH : 월
QUARTER : 분기
YEAR : 연

datediff(날짜1, 날짜2)
→ 날짜1 - 날짜2 (두 날짜 간 '일 차이'를 알려주는 함수)

'데이터 분석 > SQL' 카테고리의 다른 글

QCC 5회차  (0) 2025.05.02
QCC 4회차  (0) 2025.04.11
mysql 정규표현식, substring_index  (0) 2025.04.06
QCC 3회차  (0) 2025.04.04
[day24] window 함수 - 비율 함수  (0) 2025.03.11