내일배움캠프

[내일배움캠프] TIL 8일차 26.01.02(금)

nom_de_plume 2026. 1. 2. 19:05

sql 성취도 평가

 

Q1. SQL의 “논리 실행 순서(작동 순서)”로 가장 올바른 것은? 🧰 필수

A. SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY

B. WHERE → FROM → GROUP BY → SELECT → HAVING → ORDER BY

C. FROM → ON/JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY

D. FROM → SELECT → WHERE → ORDER BY → GROUP BY → HAVING

 

답) C. FROM → ON/JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY

해설) A. SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY 는 작동 순서가 아닌 작성 순서

 

 

Q2. 아래 테이블에서 COUNT() 결과로 올바른 것은? 🧰 필수

테이블: t_review

id review_score

1 5
2 NULL
3 3

쿼리:

SELECT 
  COUNT(*) AS c1,
  COUNT(review_score) AS c2,
  COUNT(DISTINCT review_score) AS c3
FROM t_review;

A. (3, 2, 2)

B. (3, 3, 2)

C. (2, 2, 2)

D. (3, 2, 3)

 

답) A. (3, 2, 2)

해설) COUNT(*) = review_score(5, NULL, 3)의 총 개수. 즉, 3을 의미

         COUNT(review_score) = NULL이 아닌 review_score(5, 3)의 총 개수. 즉 2를 의미

         COUNT(DISTINCT review_score) = NULL이 아니면서 서로 겹치지 않는 review_score(5, 3)의 총 개수. 즉 2를 의미

 

 

Q3. 아래 쿼리가 에러(혹은 비결정적 결과)를 유발하는 가장 적절한 이유는? 🧰 필수

SELECT
  ship_region,
  order_date,
  COUNT(*) AS order_cnt
FROM orders
GROUP BY ship_region;

A. COUNT(*)는 GROUP BY와 함께 쓸 수 없다

B. ship_region은 GROUP BY에 사용할 수 없다

C. order_date는 반드시 ORDER BY에 있어야 한다

D. SELECT에 있는 비집계 컬럼(order_date)이 GROUP BY에 포함되지 않았다

 

답) D. SELECT에 있는 비집계 컬럼(order_date)이 GROUP BY에 포함되지 않았다

해설) A. COUNT(*)는 GROUP BY와 함께 쓸 수 없지만 오류를 유발하지는 않는다.

         B. ship_region은 GROUP BY에 사용할 수 있다.

         C. 집계 함수(COUNT)를 제외한 SELECT 절의 모든 컬럼(ship_region, order_date)은 반드시 GROUP BY 절에 명시되어야 한다. 그렇지 않으면 어떤 날짜를 출력해야 할지 엔진이 판단할 수 없어 에러가 발생다.

 

 

Q4. GROUP BY가 있는 쿼리에서 집계 전에 행을 먼저 필터링하려면 어느 절을 사용하는 것이 가장 적절한가? 🧰 필수

A. HAVING

B. WHERE

C. ORDER BY

D. LIMIT

 

답) B. WHERE

해설) 이는 Q1과 같이 작동순서를 먼저 알 필요가 있다. 작동순서는 FROM -> ON/JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY 이다. 즉, GROUP BY가 있는 쿼리에서 집계 전에 행을 먼저 필터링하려면 WHERE절을 사용해야한다. HAVING은 집계 후에 행을 필터링할 때 사용한다.

 

 

Q5. UNION과 UNION ALL의 차이로 올바른 것은? 🧰 필수

A. UNION은 열을 늘리고 UNION ALL은 행을 늘린다

B. UNION ALL은 중복을 제거하고 UNION은 중복을 유지한다

C. UNION은 중복 행을 제거하고, UNION ALL은 중복 행을 유지한다

D. UNION은 ORDER BY를 절대 사용할 수 없다

 

답) C. UNION은 중복 행을 제거하고, UNION ALL은 중복 행을 유지한다

해설) A. UNION과 UNION ALL은 수직 결합이므로 행(Row)을 늘리는 방식다. 열(Column)을 늘리는 방식은 JOIN이다.

         B. UNION은 중복을 제거하고 UNION ALL은 중복을 유지한다

         D. UNION은 ORDER BY를 사용할 수 있다

 

 

Q6. 아래 쿼리가 LEFT JOIN임에도 불구하고 결과가 “INNER JOIN처럼” 보일 가능성이 가장 큰 이유는? 🚀 도전

SELECT o.order_id, p.payment_id
FROM orders o
LEFT JOIN payments p
  ON o.order_id = p.order_id
WHERE p.payment_status = 'paid';

A. LEFT JOIN은 원래 매칭 없는 행을 못 가져온다

B. ON절에는 조건을 추가할 수 없다

C. WHERE절은 조인 조건을 무시한다

D. WHERE절이 오른쪽 테이블 NULL 행을 제거해서 결과가 INNER JOIN처럼 된다

 

답) D. WHERE절이 오른쪽 테이블 NULL 행을 제거해서 결과가 INNER JOIN처럼 된다

해설) => LEFT JOIN을 유지하려면 오른쪽 테이블(p)에 대한 조건은 WHERE가 아니라 ON 절에 작성해야 한다. WHERE에 쓰는 순간 NULL 값들이 필터링되어 사실상 INNER JOIN과 같은 결과가 나온다.

         A. LEFT JOIN은  오른쪽 테이블에 매칭되는 행이 없더라도 왼쪽 테이블의 모든 행을 가져온다.

             (매칭 없는 부분은 NULL로 채워진다)

         B. ON절에는 조건을 추가할 수 있다.

         C. WHERE절은 조인 조건을 무시하지 않는다.

 

 

Q7. 아래 데이터에서 CASE 결과로 올바른 것은? 🧰 필수

테이블 일부: orders

order_id coupon_code

ord_5001 NEW10
ord_5002 NULL
ord_5008 VIP15

쿼리:

SELECT
  order_id,
  CASE
    WHEN coupon_code IS NULL THEN 'no_coupon'
    WHEN coupon_code = 'VIP15' THEN 'vip'
    ELSE 'other_coupon'
  END AS coupon_label
FROM orders
WHERE order_id IN ('ord_5001','ord_5002','ord_5008')
ORDER BY order_id;

A. ord_5001=other_coupon / ord_5002=no_coupon / ord_5008=vip

B. ord_5001=vip / ord_5002=no_coupon / ord_5008=other_coupon

C. ord_5001=no_coupon / ord_5002=other_coupon / ord_5008=vip

D. ord_5001=other_coupon / ord_5002=vip / ord_5008=no_coupon

 

답) A. ord_5001=other_coupon / ord_5002=no_coupon / ord_5008=vip

해설) NEW10은 CASE WHEN 조건에서 특정하게 조정된게 없으므로 ELSE의 'other_coupon'이 된다. 다음으로 NULL은 CASE WHEN의 첫번째 조건에 의하여 'no_coupon'이 된다. 마지막으로 VIP15는 CASE WHEN의 두번째 조건에 의하여 'vip'가 된다. 즉, 'other_coupon', 'no_coupon', 'vip' 순이 된다.

 

 

Q8. IN (subquery)를 사용할 때 가장 안전한 조건으로 올바른 것은? 🚀 도전

A. subquery는 반드시 1행만 반환해야 한다

B. subquery는 보통 1개 컬럼(값 목록)을 반환해야 한다

C. subquery에는 GROUP BY가 반드시 있어야 한다

D. subquery에는 WHERE를 쓰면 안 된다

 

답) B. subquery는 보통 1개 컬럼(값 목록)을 반환해야 한다

해설) A. IN 연산자는 여러 개의 값 중 하나라도 일치하면 참을 반환하는 연산자이다. 따라서 서브쿼리가 여러 행(Multi-row)을 반환하는 것이 일반적다.

         C. subquery에는 GROUP BY가 없어도 된다

         D. subquery에는 WHERE를 자주 쓴다

 

 

Q9. CTE(WITH)에 대한 설명으로 가장 올바른 것은? 🧰 필수

A. 실행하면 DB에 영구 테이블로 저장된다

B. 항상 GROUP BY가 포함되어야 한다

C. JOIN과 함께 사용 불가능하다

D. 단일 statement 범위에서만 존재하며, 그 statement 안에서 여러 번 참조 가능하다

 

답) D. 단일 statement 범위에서만 존재하며, 그 statement 안에서 여러 번 참조 가능하다

해설) A. 실행하면 DB에 영구 테이블로 저장되지 않는다.

         B. GROUP BY가 포함되어도 된다

         C. JOIN과 함께 사용 가능하다

 

 

Q10. MySQL에서 윈도우 함수 사용 위치로 올바른 것은? 🚀 도전

A. WHERE 절에서만 사용 가능

B. GROUP BY 절에서만 사용 가능

C. SELECT 리스트와 ORDER BY 절에서만 사용 가능

D. JOIN ON 절에서만 사용 가능

 

답)  C. SELECT 리스트와 ORDER BY 절에서만 사용 가능

해설) 윈도우 함수(ROW_NUMBER, SUM OVER 등)는 결과 집합이 결정된 이후인 SELECT 절이나 최종 정렬 단계인 ORDER BY 절에서만 사용할 수 있다. WHERE 절에서 윈도우 함수 결과를 필터링하고 싶다면 서브쿼리나 CTE(WITH)를 먼저 사용해야 한다.

 

 

Q11. 주문 리스트 라벨링 🧰 필수

orders 테이블에서 아래 조건을 만족하는 주문만 출력하세요.

  • 조건: order_date가 '2026-01-05' ~ '2026-01-15' (포함)
  • 출력 컬럼:
    • order_id
    • order_date
    • channel
    • order_status
    • coupon_flag
      • coupon_code가 NULL이면 'no_coupon'
      • 아니면 'coupon_used'
    • delivery_days_clean
      • delivery_days가 NULL이면 0, 아니면 delivery_days
  • 정렬: order_date 오름차순, order_id 오름차순

[실행 화면]

 

제출 답안)

select
	order_id,
	order_date,
	channel,
	order_status,
	case
		when coupon_code is null then 'no_coupon'
		else 'coupon_used'
	end as coupon_flag,
	case
		when delivery_days is null then 0
		else delivery_days
	end as delivery_days_clean
from test.orders
order by 
	order_date asc, 
	order_id asc;

=> 문제에서 제시한 'order_date가 '2026-01-05' ~ '2026-01-15' (포함)'조건을 누락 작성했다. 답이 같게 나온 것 같지만 데이터의 수가 훨씬 많게 출력되었다. 쿼리 작성 후 재검토 시간도 빠듯했지만 문제를 좀 더 자세히 읽지 않은 문제점이 발생했다고 본다.

 

튜터님 해설)

select
	order_id,
	order_date,
	channel,
	order_status,
	case
		when coupon_code is null then 'no_coupon'
		else 'coupon_used'
	end as coupon_flag,
	ifnull(delivery_days, 0) as delivery_days_clean
from orders
where order_date >= '2026-01-05' and order_date < '2026-01-16'
order by order_date, order_id

 

 

Q12. 지역별 매출 요약 🧰 필수

orders + order_items를 이용해서, 완료된 주문만 지역별로 요약하세요.

  • 조건: order_status = 'completed'
  • 매출(net_revenue) 정의:
  • unit_price * quantity * (1 - discount_rate)
  • 출력 컬럼:
    • ship_region
    • order_cnt : 지역별 고유 주문 수
    • customer_cnt : 지역별 고유 고객 수
    • net_revenue : 지역별 매출 합계
  • 추가 조건(HAVING): order_cnt >= 2 인 지역만
  • 정렬: net_revenue 내림차순, ship_region 오름차순
  • orders 테이블 별칭 : o / order_items 테이블 별칭 : oi

[실행 화면]

 

제출답안)

select
	o.ship_region,
	count(o.order_id) over (partition by o.ship_region) as order_cnt,
	count(o.customer_id) over (partition by o.ship_region) as customer_cnt,
	sum(unit_price*quantity*(1-discount_rate)) as net_revenue
from test.orders as o
left join test.order_items as oi
	on o.order_id = oi.order_id 
where order_status='completed'
having order_cnt >=2
order by
	net_revenue desc,
	ship_region asc;

=> 쿼리를 완성 못해 결과를 출력하지 못한다. WINDOW함수가 아닌 DISTINCT를 활용해 COUNT했어야 한다. 튜터님 해설 전 혼자 다시 풀어보았다.

 

셀프 피드백)

select
	o.ship_region,
	count(distinct o.order_id) as order_cnt,-- 고유면 distinct 사용 필요
	count(distinct o.customer_id) as customer_cnt,-- 고유면 distinct 사용 필요
	sum(unit_price*quantity*(1-discount_rate)) as net_revenue
from test.orders as o
left join test.order_items as oi
	on o.order_id = oi.order_id 
where order_status='completed'
group by o.ship_region -- 지역별이므로 지역조건 걸어주기
having order_cnt >=2
order by
	net_revenue desc,
	ship_region asc;

=> 아래를 통해 스스로 답안을 출력해낸 것을 알 수 있다. 좀 더 쉽게 생각했다면 금방 풀었을 문제라고 생각한다. 코드 출력이 안되서 당황해 어영부영 시간을 보낸게 크다. 이후 문제를 푸는데도 큰 영향을 끼치는 문제였다.

 

튜터님 해설)

select
	o.ship_region,
	count(distinct o.order_id) as order_cnt,
	count(distinct o.customer_id) as customer_cnt,
	sum(oi.unit_price * oi.quantity * (1-oi.discount_rate)) as net_revenue 
from orders o
join order_items oi
	on o.order_id = oi.order_id
where o.order_status = 'completed'
group by o.ship_region
having order_cnt >= 2
order by net_revenue desc, ship_region asc;

 

 

Q13. 결제 내역 붙이기 🚀 도전

**orders**를 기준으로 결제 정보를 붙이되, 결제가 없는 주문도 결과에 남겨야 합니다.

  • 대상 주문: order_date가 '2026-01-05' ~ '2026-01-31' (포함)
  • payments는 payment_status='paid' 인 결제만 붙이세요.
  • 출력 컬럼:
    • order_id, order_status, order_date
    • payment_id, paid_at, amount (결제 없으면 NULL)
  • 정렬: order_date, order_id, paid_at 오름차순
  • orders 테이블 별칭 : o / payments 테이블 별칭 : p

[실행 화면]

 

제출 답안)

select
	o.order_id,
	o.order_status,
	o.order_date,
	p.payment_id,
	p.paid_at,
	p.amount
from test.orders as o
left join test.payments as p
	on o.order_id  = p.order_id
		and payment_status='paid'
where order_date between '2026-01-05' and '2026-01-31'
order by
	order_date,
	order_id,
	paid_at;

=> 결제가 없는 주문도 결과에 남겨야 가 핵심이다.

 

튜터님 해설) 동일

 

 

Q14. SQL 카테고리 구매 고객 찾기 🚀 도전

아래 조건을 만족하는 고객을 찾으세요.

  • 조건:
    • 고객이 completed 주문을 한 적이 있고
    • 그 주문에 포함된 책 중 books.category = 'sql'하나라도 포함
  • 출력 컬럼: customer_id, customer_name, segment
  • 정렬: customer_id 오름차순
  • orders 테이블 별칭 : o / order_items 테이블 별칭 : oi / books 테이블 별칭 : b / customers 테이블 별칭 c
  • 힌트: EXISTS 또는 IN 방식 둘 다 허용

[실행 화면]

 

제출 답안)

select
	o.customer_id,
	c.customer_name,
	c.segment
from test.orders as o
left join test.customers as c
	on o.customer_id  = c.customer_id
left join test.order_items as oi
	on o.order_id = oi.order_id 
where o.order_status ='completed' and exists (
	select 1
	from test.books as b
	where b.book_id = oi.book_id and category='sql'
)
order by customer_id asc;

=> 동일한 데이터가 1행, 2행에 나타난다. 문제를 찾지 못해 튜터님의 해설을 필요로 한다.

 

튜터님 해설)

select
	c.customer_id,
	c.customer_name,
	c.segment
from customers c
where exists (
	select 1
	from orders o
	join order_items oi
		on o.order_id = oi.order_id
	join books b
		on b.book_id = oi.book_id
	where o.customer_id = c.customer_id
		and o.order_status = 'completed'
		and b.category = 'sql'
)
order by c.customer_id;

=> exists문 안에서 테이블 3개를 다중 join해야 한다. 이후에는 동일하다.

 

 

Q15. 고객별 주문 수를 행 유지로 붙이기 🚀 도전

**orders**에서 각 주문 행(행 유지)마다, 해당 고객의 주문 수를 붙여 출력하세요.

  • 출력 컬럼:
    • order_id, customer_id, order_date, order_status
    • customer_order_cnt : 고객별 주문 수
  • 정렬: customer_id, order_date, order_id 오름차순

[실행 화면]

 

제출 답안)

select
	order_id,
	customer_id,
	order_date,
	order_status,
	count(*) over (partition by customer_id) as customer_order_cnt
from test.orders
order by
	customer_id,
	order_date,
	order_id;

 

튜터님 해설) 동일.