코드 그라데이션

스파르타 SQL 4주차 내용 정리 본문

Database/SQL

스파르타 SQL 4주차 내용 정리

완벽한 장면 2023. 6. 14. 22:10

수업 목표

1. Subquery(서브쿼리)의 사용 방법을 배워본다

2. 실전에서 유용한 SQL 문법을 더 배워본다

3. SQL을 사용하여 실전과 같은 데이터 분석을 진행해본다.

 

Subquery: 원하는 데이터를 더 쉽게 얻어보기

👉 Subquery란? 쿼리 안의 쿼리라는 의미.

하위 쿼리의 결과를 상위 쿼리에서 사용하면, SQL 쿼리가 훨씬 간단해진다.

 

SubQuery가 익숙해지면, With 이용해서 더 훌륭한 구문을 만들 수 있다.

 

하나의 SQL 쿼리 안에 또다른 SQL 쿼리가 있는 것 을 의미.

 

kakaopay로 결제한 유저들의 정보 보기

1. users 와 orders 의 inner join으로 파악하기

select u.user_id, u.name, u.email from users u
inner join orders o on u.user_id = o.user_id
where o.payment_method = 'kakaopay'

<작성 순서>

SELECT * FROM users;

SELECT * FROM orders; 로 미리 조망 => user_id 공통

 

1) select * from users u

2) inner join orders o on u.user_id = o.user_id

3) where o.payment_method = 'kakaopay'

4) 1수정. select u.user_id, u.name, u.email from users u

 

 

조금 더 직관적으로 바꾸기

1. 우선 kakaopay로 결제한 user_id를 모두 구해보기 → K 라고 하자.

select user_id from orders
where payment_method = 'kakaopay'

 

2. 그 후에, user_id가 K 에 있는 유저들만 골라보기 => 서브쿼리

select u.user_id, u.name, u.email from users u
where u.user_id in (
select user_id from orders
where payment_method = 'kakaopay'
)

 

 괄호 안에 있는 것이 서브쿼리. 괄호 안에 있는 것만 보겠다.

 

지금 상황에서는 

select user_id from orders
where payment_method = 'kakaopay' 이거 자체가 괄호 안으로 들어갔다.

 


Subquery 본격 사용해보기

  • 안에 있는 것부터 결과를 만들고 밖으로 간다.
  • 서브쿼리는 where, select, from 절에서 유용하게 사용될 수 있다.

where에 들어가는 서브쿼리

카카오페이로 결제한 주문건 유저들만 출력하기

select * from users u
where u.user_id in (select o.user_id from orders o
where o.payment_method = 'kakaopay');

 

쿼리가 실행되는 순서

(1) from 실행: users 데이터를 가져와 줌

(2) Subquery 실행: 해당되는 user_id의 명단을 뽑아줌

(3) where .. in 절에서 subquery의 결과에 해당되는 'user_id의 명단' 조건으로 필터링 해줌

(4) 조건에 맞는 결과 출력

 

Select 에 들어가는 Subquery

  • Select는 결과를 출력해주는 부분
  • 기존 테이블에 함께 보고싶은 통계 데이터를 손쉽게 붙이는 것에 사용한다.
  • select 필드명, 필드명, (subquery) from .. 이렇게

'오늘의 다짐' 좋아요의 수가, 본인이 평소 에 받았던 좋아요 수에 비해 얼마나 높고 낮은지 알아보기

 

1. 평균 구하기

select avg(likes) from checkins c2
where c2.user_id = '4b8a10e6'

 

2. 서브쿼리 포함하여 코드 작성하기

select c.checkin_id, c.user_id, c.likes,
(select avg(likes) from checkins c2
where c2.user_id = c.user_id) as avg_like_user
from checkins c;

 

쿼리가 실행되는 순서

(1) 밖의 select * from 문에서 데이터를 한줄한줄 출력하는 과정에서

(2) select 안의 subquery가 매 데이터 한줄마다 실행되는데

(3) 그 데이터 한 줄의 user_id를 갖는 데이터의 평균 좋아요 값을 subquery에서 계산해서

(4) 함께 출력해준다

 

 

From 에 들어가는 Subquery  (가장 많이 사용)

  • From은 언제 사용하는가.
  • 내가 만든 Select와 이미 있는 테이블을 Join하고 싶을 때 사용한다.

유저별 좋아요 평균 구하기

  • checkins 테이블을 user_id로 group by 하기
select user_id, round(avg(likes),1) as avg_like from checkins
group by user_id

<작성 순서>

1) select * from checkins

2) group by user_id

3) 1수정. select user_id round (avg(likes), 1) from checkins

 

서브쿼리로 정리하면

select pu.user_id, a.avg_like, pu.point from point_users pu
inner join 
(
select user_id, round(avg(likes),1) as avg_like from checkins
group by user_id
) 
a on pu.user_id = a.user_id

a 가

select user_id, round(avg(likes),1) as avg_like from checkins
group by user_id

지금 요 테이블을 의미

 

쿼리가 실행되는 순서

(1) 먼저 서브쿼리의 select가 실행되고,

(2) 이것을 테이블처럼 여기고 밖의 select가 실행!


Subquery 연습해보기(where, select)

[연습] 전체 유저의 포인트의 평균보다 큰 유저들의 데이터 추출하기

  • 평균 포인트는 5380점.
  • point_users 테이블을 이용해서 avg를 구하고, 다시 point_users와 Join하기

select * from point_users pu

where point > 5000

 

평균 구하는 식

select avg(point) from point_users

 

포인트 평균 조건으로 Subquery 연습해보기

select * from point_users pu
where pu.point > (select avg(pu2.point) from point_users pu2);

 

 

[연습] 이씨 성을 가진 유저의 포인트의 평균보다 큰 유저들의 데이터 추출하기

  • 이씨 성을 가진 유저들의 평균 포인트는 7454점
  • 위 구문의 서브쿼리 내에서 users와 inner join 하기

전체 모양

select * from point_users pu
where point > (
-- 이 부분에 들어갈 것을 만들면 됨.

)

 

select * from point_users pu
where pu.point >
(select avg(pu2.point) from point_users pu2
inner join users u
on pu2.user_id = u.user_id
where u.name = "이**");

 

select * from point_users

select * from users =======> 공통 user_id

 

<작성 순서>

1. inner 조인으로 풀기

1) select * from point_users pu

2) inner join users u on pu.user_id = u.user_id

3) where u.name = '이**'

4) 1수정 select avg(point) from point_users pu

 

 

2. 풀이 2.

1)) select * from point_users pu

 2) where user _id in (

   3) select * from users where name ='이**'  

   )

4) 3수정 select user_id from users where name = '이**'

5) 1수정 select avg(point) from point_users pu

 

정리하면 서브 쿼리 안에 서브 쿼리

select * from point_users pu
where point > (
	select avg(point) from point_users pu
    where user_id in (
    	select user_id from users 
        where name = '이**'
    )
)

 

 

괄호 안에 들어갈 문장

(

1) select avg(point) from point_users pu

2) where user_id in (select user_id from users wher name = '이**'

)

 

 

Select 절에 들어가는 Subquery 연습해보기

[연습] checkins 테이블에 course_id별 평균 likes수 필드 우측에 붙여보기

course_id별 평균 like수 붙이기

select c.checkin_id, 
		c.title, 
        c.user_id, 
        c.likes,
		(select round(avg(c.likes),1) from checkins
        where course_id = c.course_id
        ) as course_avg
from checkins c

 

예시 답안

select c.checkin_id, c2.title, c.user_id, c.likes,
	(
    select round(avg(likes), 1) from checkins
    where course_id = c.course_id
    ) as course_avg
from checkins c    

// 이것과 courses를 조인
inner join courses c2 on c.course_id = c2.course_id

 

Subquery 연습해보기(from, inner join)

[준비 1] course_id별 유저의 체크인 개수를 구해보기

  • checkins 테이블을 course_id로 group by => distinct로 개수 세기

course_id별 체크인 개수

select course_id, count(distinct(user_id)) as cnt_checkins from checkins
group by course_id

<작성 순서>

1) select * from checkins

2) group by course_id

3) 1수정 select course_id, count(*) from checkins

4) 재수정 select course_id count(distinct(user_id) as cnt_checkins from checkins

 

[준비 2] course_id별 인원을 구해보기

  • orders 테이블을 course_id로 group by 하면 된다.
select course_id, count(*) as cnt_total from orders
group by course_id

<작성 순서>

1) select * from orders

2) group by course_id

3) 1수정 select course_id, count(*) as cnt_total from orders

 

[결과물] course_id별 좋아요 개수에 전체 인원을 붙이기

  • 준비 1과 준비 2를 inner join 하면 된다.
select * from
(
-- 준비 1
) a
inner join
(
-- 준비 2
) b on a.course_id = b.course_id

 

최종

select a.course_id, b.cnt_checkins, a.cnt_total from
(
select course_id, count(*) as cnt_total from orders
group by course_id
) a
inner join (
select course_id, count(distinct(user_id)) as cnt_checkins from checkins
group by course_id
) b
on a.course_id = b.course_id

 

[추가]퍼센트로 나타내려면?

select a.course_id, 
	b.cnt_checkins, 
        a.cnt_total, 
        (b.cnt_checkins/a.cnt_total) as ratio from -- 비율
(
select course_id, count(*) as cnt_total from orders
group by course_id
) a
inner join (
select course_id, count(distinct(user_id)) as cnt_checkins from checkins
group by course_id
) b
on a.course_id = b.course_id

 

[마무리] 강의 제목과 함께 나타내기

  • courses 테이블과 Join 하면 된다.
select c.title, -- 타이틀을 갖고 옴.
a.cnt_checkins,
b.cnt_total,
(a.cnt_checkins/b.cnt_total) as ratio
from
(
select course_id, count(distinct(user_id)) as cnt_checkins from checkins
group by course_id
) a
inner join
(
select course_id, count(*) as cnt_total from orders
group by course_id
) b on a.course_id = b.course_id
inner join courses c on a.course_id = c.course_id -- 이러면 타이틀이 붙여져 나오고

 


With절 연습하기

* with절로 더 깔끔하게 쿼리문을 정리하기

select c.title,
a.cnt_checkins,
b.cnt_total,
(a.cnt_checkins/b.cnt_total) as ratio
from (
select course_id, count(distinct(user_id)) as cnt_checkins from checkins
group by course_id
) a
inner join
(
select course_id, count(*) as cnt_total from orders
group by course_id
) b on a.course_id = b.course_id
inner join courses c on a.course_id = c.course_id

 

이렇게 계속 서브쿼리가 붙으면, inner join 안쪽이 너무 헷갈린다.

→ 그 때 쓰는 것이 with 절! 결과는 같은데 훨씬 보기 편하다.

with table1 as (
select course_id, count(distinct(user_id)) as cnt_checkins from checkins
group by course_id
), table2 as (
select course_id, count(*) as cnt_total from orders
group by course_id
)
select c.title,
a.cnt_checkins,
b.cnt_total,
(a.cnt_checkins/b.cnt_total) as ratio
from table1 a inner join table2 b on a.course_id = b.course_id
inner join courses c on a.course_id = c.course_id

 


실전에서 유용한 SQL 문법 (문자열)

문자열 쪼개보기

[문제] 이메일에서 아이디만 가져와보기

  • @를 기준으로 텍스트롤 쪼개고, 그 중 첫 번째 조각을 가져온다.
select user_id, email, SUBSTRING_INDEX(email, '@', 1) from users

[문제] 이메일에서 이메일 도메인만 가져와보기

  • @를 기준으로 텍스트롤 쪼개고, 그 중 마지막 조각을 가져온다.
select user_id, email, SUBSTRING_INDEX(email, '@', -1) from users

 

문자열 일부만 출력하기

[문제 1] orders 테이블에서 날짜까지 출력하게 해보기

select order_no, created_at, substring(created_at,1,10) as date from orders

 

[문제 2] 일별로 몇 개씩 주문이 일어났는지 살펴보기 

select substring(created_at,1,10) as date, count(*) as cnt_date from orders
group by date

 


실전에서 유용한 SQL 문법(Case)

  • 10,000점보다 높은 포인트를 가지고 있으면 '잘 하고 있어요!, 평균보다 낮으면 '조금 더 달려주세요!' 를 표기하려면?
  • 구간별 표기 하고싶다.

이게 자바에서 switch문과 비슷!!!!!

 

[문제] 포인트 보유액에 따라 다르게 표시해주기

select pu.point_user_id, pu.point,
case
when pu.point > 10000 then '잘 하고 있어요!'
else '조금 더 달려주세요!'
END as '구분'
from point_users pu;

 

Subquery를 이용한 통계 내기

1. 몇 가지로 기준을 나누고

select pu.point_user_id, pu.point,
case 
when pu.point >= 10000 then '1만 이상'
when pu.point >= 5000 then '5천 이상'
else '5천 미만'
END as level
from point_users pu

 

2. 서브쿼리를 이용하여 통계 내기

select level, count(*) as cnt from (
select pu.point_user_id, pu.point,
case
when pu.point >= 10000 then '1만 이상'
when pu.point >= 5000 then '5천 이상'
else '5천 미만'
END as level
from point_users pu
) a
group by level

 

 

3. when절 활용하여 활용점정 만들기

with table1 as (
select pu.point_user_id, pu.point,
case
when pu.point >= 10000 then '1만 이상'
when pu.point >= 5000 then '5천 이상'
else '5천 미만'
END as level
from point_users pu
)
select level, count(*) as cnt from table1
group by level

 


SQL 문법 복습. 또 복습! (초급)

[퀴즈] 평균 이상 포인트를 가지고 있으면 '잘 하고 있어요' / 낮으면 '열심히 합시다!' 표시하기

  • CASE 문법 사용, CASE 안에서 Subquery로 평균을 계산하여 비교하기
select pu.point_user_id, pu.point,
case
when pu.point > (select avg(pu2.point) from point_users pu2) then '잘 하고 있어요!'
else '열심히 합시다!'
end as 'msg'
from point_users pu

(select avg(pu2.point) from point_users pu2) = select avg(point) from point_users

 

[퀴즈] 이메일 도메인별 유저의 수 세어보기

  • Substring_Index와 Group By를 사용하면 됨.

예시답안

select domain, count(*) as cnt from (
select SUBSTRING_INDEX(email,'@',-1) as domain from users
) a
group by domain

 

[퀴즈] '화이팅'이 포함된 오늘의 다짐만 출력해보기

  • like 활용하기

예시답안

select * from checkins c
where c.comment like '%화이팅%'

 


SQL 문법 복습. 또 복습! (중급)

[퀴즈] 수강등록정보(enrolled_id)별 전체 강의 수와 들은 강의의 수 출력해보기

  • subquery 두 개를 만들어놓고, inner join
  • 살펴볼 테이블: enrolled_details
  • done_cnt는 들은 강의의 수(done=1)
  • total_cnt는 전체 강의의 수

작성 순서

1) select * from enrolldes_ detail

2) where done = 1

3) group by enrolled_id

4) 1수정 select enrolled_id, count(*) from enrolleds_detail

5) 1또수정 select enrolled_id, count(*) from as done_cnt enrolleds_detail

 

2번째 작업 - done 빼기

1) select enrolled_id, count(*) from as total_cnt enrolleds_detail

2) group by enrolled_id

 

이제 서브쿼리로 잇기

select *from (
	select enrolled_id, count(*) as cnt_done from enrolleds_detail ed
	where done = 1
	group by enrolled_id
) a 
inner join (
	select enrolled_id, count(*) as cnt_total from enrolleds_detail ed
	group by enrolled_id
) b on a.enrolled_id = b.enrolled_id
select a.enrolled_id, a.done_cnt, b.total_cnt from (
	select enrolled_id, count(*) as cnt_done from enrolleds_detail ed
	where done = 1
	group by enrolled_id
) a 
inner join (
	select enrolled_id, count(*) as cnt_total from enrolleds_detail ed
	group by enrolled_id
) b on a.enrolled_id = b.enrolled_id
select a.enrolled_id, a.done_cnt, b.total_cnt from (
	select enrolled_id, count(*) as cnt_done from enrolleds_detail ed
	where done = 1
	group by enrolled_id
) a 
inner join (
	select enrolled_id, count(*) as cnt_total from enrolleds_detail ed
	group by enrolled_id
) b on a.enrolled_id = b.enrolled_id

select a.enrolled_id, a.done_cnt, b.total_cnt from table1 a
inner join table2 b on a.enrolled_id = b.enrolled_id

 

 

with 절로 변경하기

with table1 as (
	select enrolled_id, count(*) as cnt_done from enrolleds_detail ed
	where done = 1
	group by enrolled_id
), table2 as (
	select enrolled_id, count(*) as cnt_total from enrolleds_detail ed
	group by enrolled_id
)

 

정답 쿼리

with lecture_done as (
select enrolled_id, count(*) as cnt_done from enrolleds_detail ed
where done = 1
group by enrolled_id
), lecture_total as (
select enrolled_id, count(*) as cnt_total from enrolleds_detail ed
group by enrolled_id
)
select a.enrolled_id, a.cnt_done, b.cnt_total from lecture_done a
inner join lecture_total b on a.enrolled_id = b.enrolled_id

 

 

[퀴즈] 수강등록정보(enrolled_id)별 전체 강의 수와 들은 강의의 수, 그리고 진도율 출력해보기

  • 진도율 = (들은 강의의 수 / 전체 강의 수)
with table1 as (
select enrolled_id, count(*) as done_cnt from enrolleds_detail
where done = 1
group by enrolled_id
), table2 as (
select enrolled_id, count(*) as total_cnt from enrolleds_detail
group by enrolled_id
)
select a.enrolled_id,
a.done_cnt,
b.total_cnt,
round(a.done_cnt/b.total_cnt,2) as ratio
from table1 a
inner join table2 b on a.enrolled_id = b.enrolled_id

 

더 간단하게 만들기

select enrolled_id,
sum(done) as cnt_done,
count(*) as cnt_total
from enrolleds_detail ed
group by enrolled_id

 

 

728x90
Comments