오늘 학습한 내용을 나만의 언어로 정리하기
다음 주부터 SQL 뿐만 아니라 python 학습도 병행하게 된다. 이에 앞서 기본적인 개념들만 간단하게 확인하고자 내일배움캠프에서 지급해주는 python 강의 영상을 시청했다.
무슨 일을 하든지, 기본이 가장 중요하다. 그런 의미로 이번 주차에 학습했던 내용들을 상기시키며 내가 올바르게 이해한 것이 맞는지 다시 한 번 확인하는 의미로 어제까지 배웠던 내용들을 리마인드 하고자 했다.
SQL 또한 앞으로도 계속 사용할 예정이다. 그리고 꽤 익숙해졌다고는 해도, 아직 SQL을 다루는 데에는 초보자 수준에 불과하기 때문에 기초를 끊임없이 다져줘야 한다고 생각한다. 따라서 오늘은 투터님의 라이브 세션이 없는 날이기도 하니, 지금까지 배웠던 것들을 재점검하는 시간을 가져보았다.
학습 주제: python 코딩 맛보기, 현재까지 배운 SQL 기초 개념 리마인드
핵심 개념 정리:
SQL 복습은 이전 포스트에서 한 번 다뤘던 내용들이다. 따라서 이번 TIL에서는 문제와 내가 풀어낸 답을 확인해보는 과정을 간단하게 진행하도록 하겠다.
1. where 로 특정 조건을 만족하는 데이터 가져오기
성별이 '여자'인 데이터
select *
from basic.theglory
where 성별 ='f'
;

이름이 '문동은'이거나 '주여정'인 경우 역할을 '선'으로, 아닌 경우 '악'으로 설정
select *, case when 이름 in('문동은', '주여정') then '선'
else '악' end as '역할구분'
from basic.theglory
;

3. 조건문에 연산자 사용하기
날짜 데이터가 비어있지 않고, 성별이 남자가 아니고, 직업이 아나운서 또는 승무원 또는 화가이고, 나이가 30세에서 40세 사이(이상 , 이하)인 데이터
select *
from basic.theglory
where (날짜 is not null)
and (성별!='m')
and (직업 in ('아나운서', '승무원', '화가'))
and (나이 between 30 and 40)
;

4. order by로 결과값 정렬하기
select 날짜, 이름, 성별, 나이, 직업
from basic.theglory
order by 1 desc
;
실행값 1

작성한 코드 2
select 날짜, 이름, 성별, 나이, 직업
from basic.theglory
order by 1 desc, 3 asc
;


5. where 구문의 활용
'first_login_date' 컬럼이 '2023-01-01'를 초과하는 데이터의 'game_account_id', 'game_actor_id', 'serverno' 컬럼 추출하기
select game_account_id, game_actor_id, serverno
from basic.users
where first_login_date>'2023-01-01'
;

6. where 구문의 응용
아래 조건들을 모두 만족하는 데이터의 first_login_date, ip_addr, exp, zone_id 를 추출하고 first_login_date을 기준으로 내림차순으로 정렬하기
조건1) level 컬럼이 10 초과
조건2) serverno컬럼이 1이 아님
조건3) 아이템 이름 컬럼이 레벨업 패키지 또는 시즌패스
조건4) 아이템 획득 경로가 상점에서 구매한 경우
select first_login_date, ip_addr, exp, zone_id
from basic.users
where (level>10)
and (serverno!=1)
and (etc_str2 in('레벨업 패키지', '시즌패스'))
and (etc_str1='상점에서 구매')
order by first_login_date desc
;

7. case when 구문의 활용
아래 조건을 만족하는 데이터의 game_actor_id, level, levelgroup, first_login_date 컬럼을 추출하고, first_login_date를 기준으로 내림차순 정렬하기
조건1) case when 구문을 사용하여 레벨구간을 아래와 같이 구분, 컬럼이름을 ‘levelgroup’ 으로 설정
[레벨구간]
◦ 1~10Lv 이하
◦ 11~20Lv 이하
◦ 21~30Lv 이하
◦ 31~40Lv 이하
◦ 41~50Lv 이하
◦ 51~60Lv 이하
◦ 61~70Lv 이하
◦ 71~80Lv 이하
◦ 81~90Lv 이하
◦ 91~100Lv
select game_actor_id, level,
case when level <=10 then '1~10Lv 이하'
when level <=20 then '11~20Lv 이하'
when level <=30 then '21~30Lv 이하'
when level <=40 then '31~40Lv 이하'
when level <=50 then '41~50Lv 이하'
when level <=60 then '51~60Lv 이하'
when level <=70 then '61~70Lv 이하'
when level <=80 then '71~80Lv 이하'
when level <=90 then '81~90Lv 이하'
else '91~100Lv' end as levelgroup, first_login_date
from basic.users
order by first_login_date desc
;

8. 집계함수 및 그룹화 활용하기
성별을 기준으로 전체 데이터의 갯수, 평균 나이, 최대 나이, 최소 나이, 나이 합계 구하기
select 성별,
count(*) as cnt,
avg(나이) as '평균 나이',
max(나이) as '최대 나이',
min(나이) as '최소 나이',
sum(나이) as '나이 합계'
from basic.theglory
group by 1
;

9. having으로 그룹화에 의한 데이터 결과 필터링하기
나이가 31 이상, 성별을 기준으로 평균 나이를 계산하고, 평균 나이가 41 초과인 경우 구하기
-작성한 코드
select 성별,
avg(나이) as '평균 나이'
from basic.theglory
where 나이>=31
group by 1
having avg(나이)>41
;

10. 인라인 뷰 subquery 활용하기
나이가 33세 이상인 모든 데이터 중 나이와 직업 컬럼 반환하기
select a.나이, a.직업
from (select *
from basic.theglory
where 나이>=33
) as a
;

11. 집계함수의 활용
아래 조건을 만족시켜 월별, 서버별 게임계정id 수를 중복값 없이 추출하기
*월은 첫 접속일자 컬럼을 기준으로
조건1) 월은 yyyy-mm의 형태로 추출
select substr(first_login_date, 1, 7) as m,
serverno,
count(distinct game_account_id) as usercnt
from basic.users
group by 2, 1
;

12. 집계함수와 조건절의 활용 1
group by 를 활용해 첫 접속일자별 게임 캐릭터 수를 중복값 없이 구하고, having 절을 사용해서 그 값이 10개를 초과하는 경우만 추출하기
select first_login_date,
count(distinct game_actor_id) as actor_cnt
from basic.users
group by 1
having count(distinct game_actor_id)>10
;

13. 집계함수와 조건절의 활용 2
group by 절을 사용하여 서버별, 유저구분(기존/신규)별 게임캐릭터id수 및 평균레벨을 추출하기(게임캐릭터id수는 중복값을 허용하지 않는 고유한 갯수로 추출할 것)
조건1) 기존/신규 기준→ 첫 접속일자가 2024-01-01 보다 작으면(미만) 기존유저, 그렇지 않은 경우 신규유저
select serverno,
case when first_login_date<'2024-01-01' then '기존유저'
else '신규유저' end as gb,
count(distinct game_actor_id) as actor_cnt,
avg(level) as avg_level
from basic.users
group by 1, 2
;

14. subquery의 활용
12번 문제를 having 이 아닌 인라인 뷰 subquery를 사용하여 추출하기
select *
from( select first_login_date,
count(distinct game_actor_id) as actor_cnt
from basic.users
group by 1
) as a
where a.actor_cnt>10
;

위 사진을 통해 해당 코드의 실행값이 문제 12번의 실행값과 동일한 결과를 추출한 것을 확인할 수 있다.
15. subquery의 응용
select a.actor_cnt,
count(*) as accnt
from ( select u.game_account_id,
count(distinct u.game_actor_id) as actor_cnt
from basic.users as u
where level>=30
group by 1
having count(distinct u.game_actor_id)>=2
) as a
group by 1
order by 1
;

이번 학습을 진행하면서 느낀 점
모든 문제에 대해서 조건에 부합하는 데이터를 추출하는 것에 성공했다.
하지만 눈에 보이는 데이터가 원하던 데이터와 완전히 부합한다고 생각하는 것은 위험하다. 같은 값이더라도, 그 데이터가 포함하는 의미는 다를 가능성이 존재한다.
가령 15번 문제의 경우 인라인 뷰 서브쿼리의 having 에서
"count(distinct u.game_actor_id)" 가 아니라
"count(game_account_id)" 를 입력한다고 해도 같은 실행값을 가지게 된다.
이것이 문제가 되는 이유는 둘이 데이터를 추출할 때 대상이 되는 데이터가 다르기 때문이다.
"having count(distinct u.game_actor_id)>=2" 는 "계정이 가지고 있는 캐릭터가 동일한 캐릭터를 제외하고 2개 이상인 경우"를 뜻한다.
"having count(game_account_id)>=2" 는 "계정 데이터의 행 수가 2개 이상인 경우"를 뜻한다.
즉, 전자일 때는 15번 문제의 두 번째 조건인 "계정별 캐릭터 수가 2개 이상인 경우만 추출"하는 것을 만족하지만, 후자일 때는 "계정별 캐릭터 수가 2개 이상인 경우"가 아니라 "계정이라는 데이터의 값이 2개 이상 있을 경우"를 의미하기 때문에 정확히 조건과 일치하지 않는다.
쉽게 말해 후자는 "동일한 게임 캐릭터이지만 레벨이 다른 경우"도 포함하고 있기 때문에 조건에 완전히 부합하지는 않는다고 할 수 있다.
이렇듯 추출된 값이 동일해보인다 해도, 그 내용을 당장에 완전히 신뢰할 수는 없다. 아무리 잘 쓰인 코드라도, 의뢰와는 동떨어진 의미를 가질 수 있기 때문이다. 이는 데이터의 신뢰성에 부정적인 영향을 주게 되고, 무엇보다 작업 시간을 대폭 늘리는 결과를 초래할 수 있다.
이를 방지하기 위해서 다른 팀원들과의 크로스 체크를 습관화하고, 다른 것보다도 데이터 리터러시 능력을 길러야만 할 것이다.
내일 학습할 것은 무엇인지
내일은 튜터님의 라이브 세션이 예정되어 있다. 따라서 내일 진행되는 강의 내용을 따라서 실습을 진행하게 될 듯하다.
마치며 : 데이터 분석가 캠프 과정을 시작한 지 어느덧 열흘 째가 되었다. 무엇인가에 집중하고 몰두하니 시간이 참 빠르게 지나간다. 시간이 흘러 프로젝트를 진행하게 되는 날에는 이 감각이 더욱 강해질 것이다. 그러고보니 벌써 10월도 끝나간다. 올 한 해를 잠시 뒤돌아보고, 남은 두 달을 어떻게 보내야할 지 잠깐 생각해보고, 다시 힘차게 살아가보자.