[SQL_입문]/개발일지

3. EXCEL보다 쉬운가..? SQL!

Code_Otaku 2022. 9. 18. 05:01

 

예아.. 정말 오래간만에 포스팅을 올리는 거 같다.

왜냐면 그 사이에 필자가 취업을 했기 때문이다.

정확하게는 이직을 한 거지만..

어차피 커리어는 리셋되어서 제로부터 시작하는 거나 마찬가지이다.

그래서 그런지 페이스를 조절하기가 참 어려웠다.

...

솔직하게 말해서 여전히 어렵기는 하다.

그래도 이제 슬슬 짬을 내서 과외 활동을 해보려고 한다.

공부 역시 거기에 해당된다고 할 수 있지.

아니, 다른 무엇보다 공부가 최우선이어야 한다.

그래서 연휴도 끼었겠다, 한동안 손에서 놨던 블로그 포스팅을 다시 시작하려고 한다.

 

저번 시간까지 우리는 엑셀보다 쉽다는 SQL을 짬짬이 공부해봤다.

데이터를 만지는 과정 중에서도 조회 (SELECT) 기능에만 중점을 두자고 했지..

이 블로그를 보러 오시는 분들이나, 필자나..

데이터를 조작 (UPDATE / DELETE) 할 수 있는 권한은 아직 없기 때문이라고 했다.

 

그래서 SELECT 하나만 제대로 다뤄보자고 했다.

그 일환으로 GROUP BY 절을 연습해봤지.

이번 시간부터는 내용이 한층 더 어려워질 것이다.

이게 정말 엑셀보다 쉬운 거 맞아?

생각이 절로 들 수도 있다.

팔자려니.. 생각해라.

 

미리 스포일러를 하자면 이번 시간에 다뤄볼 내용은 바로 JOIN과 SUBQUERY이다.

(인간적으로 서브쿼리는 다음 시간에 다뤄보도록 하자..)

스포 해도 상관없잖아?

자! 그럼 달려보도록 하자..

 


1. JOIN

 

쪼인 (JOIN)..

특정한 단체나 조직, 결사  따위에 가입하다? 회맹 하다?

문과생다운 답변이지만 사전상으로는 맞는 말이다.

SQL상에서도 딱히 틀렸다고 할 수는 없지..

그렇다고 정확한 것은 절대 아니다.

 

SQL에서의 쪼인은 서로 다른 테이블을 이어주는 (Bridge) 작업을 뜻한다.

마치 원래부터 하나의 테이블이었던 것처럼 말이다.

 

SELECT * FROM users u limit 10;

SELECT * FROM orders o limit 10;

자.. 여기 이렇게 서로 다른 테이블이 각각 하나씩 놓여있다.

이 둘 사이에는 정말로 아무런 접점도 존재하지 않을까?

한 번 출력해봐서 그 결과를 확인해보자.

 

[USERS TABLE]

 

[ORDERS TABLE]

 

뭔가 같은 것 같으면서도 다르지?

눈을 크게 뜨고 살펴보면 칼럼이 서로 일치하는 부분이 있을 것이다.

바로.. order_no 칼럼과 email 칼럼이 서로 일치하지.

 

자! 이제부터 필자가 요술을 부려 보겠다.

쪼인은 서로 다른 테이블을 마치 하나의 테이블처럼 이어주는 작업이라고 했지?

그 짓을 해보겠다는 것이다.

 

 

테이블이 너무 길어져서 하나의 사진 파일에 다 담을 수가 없었다.

말인즉슨 원래 같으면 한 화면에 다 들어왔을 서로 다른 두 개의 테이블이

마치 하나의 테이블처럼 붙었다는 증거가 될 수 있겠다.

엑셀 같으면 상당히 고된 작업이 됐을 터인데..

약간의 명령어를 추가해줌으로써 아주 간단하게 해결됐다.

 

SELECT * FROM users u
inner join orders o on u.user_id = o.user_id
LIMIT 12;

바로 이렇게 말이다!

두 번째 라인을 집중해서 보도록 할까?

이전에 보지 못한 새로운 문장일 것이다.

바로 inner join!

안쪽에서 테이블을 이어준다고 생각하면 이해가 쉬울까?

우리가 앞으로 쪼인을 쓰게 된다면 가장 많이 다루게 될 녀석이다.

 

아직 끝난 게 아니다.

on이라는 단어가 보이는가?

SQL을 처음 접해보는 분들이라면 무심코 지나칠 수 있는 부분이다.

하지만 테이블과 테이블 사이에 on이 빠져버리면 100% 오류가 뜰 것이다.

 

SQL Error [1064] [42000]: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'u.user_id = o.user_id
LIMIT 12' at line 3

Error position: line: 2

거 내가 뜬다고 했잖아..

가장 흔하게 우리의 뒷목을 잡게 만드는 syntax 에러이다.

users 테이블과 orders 테이블은 원래 같으면 서로 만날 수가 없는데..

그 둘 사이에 다리를 놓아준 셈이다.

 

아직 하나 더 남았으니까 긴장을 풀지 말아라.

 

u.user_id = o.user_id

users 테이블과 orders 테이블이 공유하고 있는 칼럼이 그냥 user_id일 뿐이다.

그런데 곧이곧대로 user_id로 묶어준다면 어느 테이블의 칼럼을 지칭하고 있는 것인지 구분이 안될 것이다.

그래서 알아보기 쉽게 별칭을 지정해준 것이지.

 

 

한 마디로 쪼인 = 교집합으로 이해하면 된다.

응용하는 게 어려울 뿐이지, 내용 자체는 이해 못 할 부분이 하나도 없다.

그러니까 이제부터 필자와 함께 응용을 한 번 해보자.

예제를 풀면서 내용을 반복 & 숙달을 해보자는 것이지.

SQL은 IQ (능지) 문제이기 때문에 어렵게 느껴질 수도 있다.

잘 따라오길 바란다.

 


1-1. 결제 수단 별 유저 포인트의 평균값 구해보기

 

[연습문제 1-1]

 

결과를 먼저 놓고 보자.

이번 연습문제에서 우리가 최종적으로 얻고자 하는 데이터는 딱 두 종류라고 할 수 있다.

하나는 결제 수단 (payment_method) 일 것이며..

다른 하나는 각 결제 수단을 사용하는 유저들의 평균 점수 (avg_point) 일 것이다.

 

어렵게 생각할 거 없이 테이블을 하나씩 조회해보면 감각적으로 문제를 풀 수가 있다.

 

 

프로그래밍을 하는 사람이 감각적으로 문제를 풀려고 하다니..

뭐 어쩌겠는가?

SQL은 필요한 데이터에 접근하기 위해서 고안한 문법이다.

우리가 직접 아키텍처를 코딩한 게 아닌 이상 센스를 발휘해야 한다.

 

SELECT * FROM orders o;

SELECT * FROM point_users pu;

필자는 우선 값이 있을 법한 테이블을 각각 조회해봤다.

payment_method와 point가 실제로 있는 값인지 확인을 해봐야겠지?

 

[ORDERS TABLE]

 

[POINT_USERS TABLE]

 

참으로 다행히도 우리에게 필요한 데이터를 한 방에 때려 맞췄다.

진짜로 이렇게 때려 맞춰 나가는 식으로 데이터를 뽑아다가 쓰는 것이다. 

엑셀을 잘 다루는 분들이 유리할 수밖에 없으며..

그런 분들은 약간의 문법 지식만 가지고 있다면 SQL이 더 쉽게 느껴질 것이다.

 

자! 여기까지 왔으면 사실상 문제의 절반을 푼 셈이다.

벌써부터 어렵게 느껴지더라도 절대로 좌절할 필요 없다.

그냥 여러분과 내가 돌대가리이거나..

센스가 약간 부족한 것뿐이다.

반복 & 숙달 외에는 답이 없다.

 

이제 아까 배운 내용을 토대로 뼈대 위에 살을 조금씩 붙여 나가 보자.

 

SELECT o.payment_method, ROUND(AVG(pu.`point`)) FROM orders o
inner join point_users pu on o.user_id = pu.user_id
GROUP BY o.payment_method
kakaopay	7729
CARD		5134
TAXBILL		4588
MONEY		3684

다 써놓고 보니까 한 번에 샥~ 하는 느낌으로 풀어버린 것 같지?

보기에만 그럴 뿐이다.

필자 역시 여기저기 와리가리 치면서 넣고, 빼고를 반복하여 저 결과를 얻어낸 것이다.

헷갈리는 게 정상이니까 필자와 함께 한 줄씩 블록을 조립해 나가 보자.

 

SELECT * FROM orders o;

SELECT * FROM point_users pu;

시작은 당연히 테이블을 하나씩 조회하는 것에서부터이다.

 

SELECT * FROM orders o
inner join point_users pu on (...)

그다음은 배운 내용대로 inner join을 체결해 주었다.

on으로 브리지를 만들어 주는 것을 잊지 말자.

 

o.user_id = pu.user_id

두 테이블을 이어 주기 위한 공통된 값을 찾았다.

여기서는 user_id 칼럼인가 보다.

 

GROUP BY o.payment_method

그런 다음은 결제 수단 별로 테이블을 정렬해줘야겠지.

저번 시간에 배운 내용이기 때문에 따로 또 지면을 할애하지는 않겠다.

 

SELECT o.payment_method, ROUND(AVG(pu.`point`)) as avg_point

우리에게 필요한 데이터는 딱 두 가지라고 했지?

orders 테이블에 속해 있는 결제 수단 칼럼을 먼저 보려고 하는 것이고..

다음으로는 point_users 테이블에 속해 있는 점수 칼럼에서 평균값을 낸 것이다.

바로 뒤에 따라붙는 as는 Alias라고 한다.

그냥 avg_point라는 별칭을 부여하기 위한 문법일 뿐이다.

 

SELECT o.payment_method, ROUND(AVG(pu.`point`)) as avg_point 
  FROM orders o
 inner join point_users pu on o.user_id = pu.user_id
 GROUP BY o.payment_method;

그렇다면 최종적으로 이렇게 완성된 형태의 SQL문이 구축될 것이다.

하나씩 뜯어보니까 어려운 거 없지?

마지막으로 예제 하나만 더 풀어보자.

 


1-2. 과목 별로 시작하지 않은 유저들을 세어보기

 

[연습문제 1-2]

 

예아! 최종적으로 뽑고자 하는 데이터는 다음과 같다.

course_id는 과목에 부여된 고유한 id 값이다.

딱 봐도 캐시 (Caeche) 같다.

title은 보아하니 과목의 명칭 같다.

cnt_nostart는 아직 한 과목도 수강하지 않는 놈들을 따로 집계한 것으로 보인다.

이런 불성실한 친구들을 봤나.. ㅡㅡ

 

SELECT * FROM courses c;

[COURSES TABLE]

 

이 한 테이블만 하더라도 필요한 칼럼이 두 개씩이나 있지?

하나는 course_id, 다른 하나는 title..

역시 필자의 감각은 짐승적.. 이기는 개뿔!

진즉에 테이블 하나 잘못 뽑아서 제로부터 다시 시작하는 거다.

SQL이 이렇다니까?

머리가 나쁘다면 필자처럼 고생 좀 하면 돼.

 

SELECT * FROM enrolleds e;

[ENROLLEDS TABLE]

 

이 테이블에서 쓸만한 데이터는 is_registered 정도가 되겠다.

종류에 상관없이 한 번이라도 강의를 들은 놈들은 1.

단 한 번도 강의를 수강한 적이 없는 게으른 놈들은 0.

이 놈들을 집계해서 평균을 내보겠다고 한다.

 

오케이! 필요한 테이블을 세팅했으니 이제 하나씩 뼈대를 세워보자.

SQL은 이리저리 널브러져 있는 레고 블록을 짜 맞추는 것과 같다.

인내심이 필요함..

 

SELECT * FROM courses c
inner join enrolleds e on c.course_id = e.course_id
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0c622f3606e60009a128d9	2020-07-13 22:31:28	2020-07-13 22:31:28	5f0ae408765dae0006002816	3af5bc14	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0d33063606e60009a12aba	2020-07-14 13:22:30	2020-07-14 13:22:30	5f0ae408765dae0006002816	65b2fd34	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0d33b83606e60009a12b1a	2020-07-14 13:25:29	2020-07-14 13:25:29	5f0ae408765dae0006002816	1f301966	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0d352e3606e60009a12cb7	2020-07-14 13:31:42	2020-07-14 13:31:42	5f0ae408765dae0006002816	03667091	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0d935d3606e60009a12fc7	2020-07-14 20:13:34	2020-07-14 20:13:34	5f0ae408765dae0006002816	14e6200d	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0dc7c63606e60009a130ad	2020-07-14 23:57:11	2020-07-14 23:57:11	5f0ae408765dae0006002816	5aafaccc	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0df73c3606e60009a13115	2020-07-15 03:19:40	2020-07-15 03:19:40	5f0ae408765dae0006002816	d9efc5c8	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0e4add3606e60009a131db	2020-07-15 09:16:29	2020-07-15 09:16:29	5f0ae408765dae0006002816	7e9b4584	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0e93130255250009673ceb	2020-07-15 14:24:36	2020-07-15 14:24:36	5f0ae408765dae0006002816	122a2530	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0eb0a6a51725000934c6a1	2020-07-15 16:30:47	2020-07-15 16:30:47	5f0ae408765dae0006002816	ac64b24c	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0ec5fdc9b9fd0009d79c34	2020-07-15 18:01:49	2020-07-15 18:01:49	5f0ae408765dae0006002816	c4c9ce25	1
5f0ae408765dae0006002816	2020-07-12 19:20:56	2020-07-12 19:20:56	웹개발 종합반	웹서비스 개발의 A-Z를 이해하고,@실제 서비스를 만들어봅니다.	5f0f26e033c41500091f65b9	2020-07-16 00:55:12	2020-07-16 00:55:12	5f0ae408765dae0006002816	ed12149c	1

여기까지는 뭐 이제 뻔하지 않는가?

course_id를 기준으로 테이블을 하나로 묶어줬지?

이다음부터가 머리싸움인 거지..

 

SELECT c.course_id, 
	   c.title,
	   COUNT(*) as cnt_notstart 
  FROM courses c
 inner join enrolleds e on c.course_id = e.course_id
 WHERE e.is_registered = 0
 GROUP by c.title

슬슬 요령이 붙지 않는가?

필자도 여기까지 조립하는 데 그리 오래 걸리지 않았다.

우리에게 필요한 데이터는 딱 세 가지이기 때문에 눈에 보이는 대로 *와 교체해줬다.

딱 하나 난해할 수도 있는 부분이 바로 수업을 듣지 않는 놈들을 집계하는 부분이었지.

간단하게 WHERE 절로 분류해줬다.

마지막은 과목별로 묶어서 정렬하기 위해 GROUP BY 절을 써준 것이고..

 


필자가 준비한 예제는 여기까지이다.

어렵게 느껴졌던 분들도 계실테고, 아직까지는 할만하다고 느끼신 분들도 있을 것이다.

여기까지만 다뤄줘도 SQL은 확실히 유용한 도구가 맞지?

하지만 아직까지 우리는 SQL의 진가를 다 맛보지 못했다.

바로 SUBQUERY가 남아있기 때문이지.

 

어렵다.

필자에겐 확실히 어렵게 다가왔다.

그러니까 긴장 바짝들 하고 오시라.

안그래도 지저분한 SQL문을 더 지저분하게 만들어주는 녀석이다.

이러니까 JPA를 쓰는 거지..

 

'[SQL_입문] > 개발일지' 카테고리의 다른 글

2. EXCEL처럼 쓰자, SQL!  (0) 2022.09.01
1. EXCEL보다 쉽다, SQL!  (0) 2022.08.25