👉두 테이블의 공통된 정보(=key값)를 기준으로 테이블을 연결해서 한 테이블처럼 보는 것을 의미. 엑셀의 vlookup과 같다!
어디에/무엇을 붙일건지, 순서가 중요
SELECT * FROM orders o
INNER JOIN users u
ON o.user_id = u.user_id;
※ 주문을 하기 위해서는 회원정보가 있어야 할테니, orders 테이블에 담긴 user_id는 모두 users 테이블에 존재함.
※ 위 쿼리가 실행되는 순서: from → join → select
(join은 항상 from과 붙어 있다.)
연결의 기준이 되고싶은 테이블을 from 절에,
기준이 되는 테이블에 붙이고 싶은 테이블을 Join 절에 위치!
'오늘의 다짐' 정보에 과목 정보를 연결해 과목별 '오늘의 다짐' 갯수를 세어보려면,
SELECT c2.title, COUNT(*) as cnt_title
FROM checkins c
INNER JOIN courses c2
ON c.course_id = c2.course_id
GROUP BY c2.title ;
결과 👉
유저의 포인트 정보가 담긴 테이블에 유저 정보를 연결해서, 많은 포인트를 얻은 순서대로 유저의 데이터를 뽑으려면,
SELECT * FROM point_users pu
INNER JOIN users u
ON pu.user_id = u.user_id
ORDER BY pu.point DESC ;
결과 👉
주문 정보에 유저 정보를 연결해 네이버 이메일을 사용하는 유저 중, 성씨별 주문건수를 세어보려면,
SELECT u.name, COUNT(u.name) as name_cnt
FROM orders o
INNER JOIN users u
ON o.user_id = u.user_id
WHERE o.email LIKE '%naver.com'
GROUP BY u.name ;
결과 👉
위 쿼리가 실행되는 순서: from → join → where → group by → select
1. from : orders 테이블 데이터 전체를 가져옴.
2. join : users와 orders 테이블 연결, orders 테이블의 user_id와 동일한 user_id를 갖는 users 테이블 데이터를 붙임. (users 테이블에 u라는 별칭)
3. where : users 테이블 email 필드값이 naver.com으로 끝나는 값만 가져옴.
4. group by : users 테이블의 name값이 같은 값들을 그룹화.
5. select : users 테이블의 name 필드와 name 필드를 기준으로 뭉쳐진 갯수를 세어서 출력.
SELECT o.payment_method, ROUND(AVG(pu.point),2) as point_avg
FROM point_users pu
INNER JOIN orders o
ON pu.user_id = o.user_id
WHERE pu.point
GROUP BY o.payment_method ;
결과 👉
평균값에서 나타내고자 하는 자리까지만 round(__,_)로 나타내준다. 위 값은 소수점 아래 2자리수까지 표기한 것이고, 정수만 표기하려면 round(avg(pu.point),0)로 작성하면 된다.
SELECT u.name, COUNT(u.name) as name_cnt
FROM enrolleds e
INNER JOIN users u
ON e.user_id = u.user_id
WHERE is_registered = 0
GROUP BY u.name
ORDER BY name_cnt DESC ;
결과 👉
_시작하지 않은_ => is_registered = 0
SELECT c.title, count(*) as title_cnt
FROM courses c
INNER JOIN enrolleds e
ON c.course_id = e.course_id
WHERE is_registered = 0
GROUP BY c.title ;
결과 👉
SELECT title, week, COUNT(*) as week_cnt
FROM courses c1
INNER JOIN checkins c2
ON c1.course_id = c2.course_id
GROUP BY title, week
ORDER BY title, week;
※ 위처럼 작성해도 정답은 나오지만 title과 week가 각각 어느 테이블에 속한 컬럼인지 표기하는 것이 쿼리가 길어졌을 때 헷갈리지 않는다. 따라서 보기 좋게 아래처럼 수정해주는 것이 좋다.
SELECT title, week, COUNT(*) as week_cnt
FROM courses c1
INNER JOIN checkins c2
ON c1.course_id = c2.course_id
GROUP BY c1.title, c2.week
ORDER BY c1.title, c2.week;
꿀팁! → group by, order by에
콤마
로 이어서 두 개 필드를 건다. EX8을 예시로 보면, title과 week로 각각 그룹화한 후 각각 정렬할 수 있다. (꼭 필드 하나만 적용하는 것이 아님!!)
: inner join이라면 공통분모가 나오지만 left join 같은 경우, 왼쪽 테이블을 기준으로 차집합의 형태로 값이 나오므로 NULL값이 존재할 수 있다.
ex) users 테이블에 point_users 테이블을 left join으로 연결했을 때, 유저 중 강의를 시작하지 않은 사람들은 포인트가 적립되지 않았을 것이므로 데이터가 표시되지 않을 것이다( [NULL]
값)
select * from users u
left join point_users pu on u.user_id = pu.user_id
따라서, 시작하지 않은 유저를 검색하려면 is null
키워드를 이용할 수 있다.(반대의 경우 is not null
입력)
그리고 count
는 NULL을 세지 않는다.
select name, count(*) from users u
left join point_users pu on u.user_id = pu.user_id
where pu.point_user_id is NULL
group by name
SELECT COUNT(pu.point_user_id) as pnt_cnt,
COUNT(*) as tot_cnt,
ROUND(COUNT(pu.point_user_id)/COUNT(*),2) as ratio
FROM users u
LEFT JOIN point_users pu ON u.user_id = pu.user_id
WHERE u.created_at BETWEEN '2020-07-10' AND '2020-07-20'
결과 👉
- count는
null
을 세지 않으므로, 전체(=202)에서 포인트를 가진 고객의 숫자를 count하면 null을 제외한 값(=82)이 나온다. 따라서 82/202를 round로 소수점을 정리해주면(두 자리까지 구한다고 했을 때), 0.41이라는 비율이 나온다.- count와 round 등 여러 조건이 붙어 컬럼명이 길어지므로 적절한 alias를 이용해준다.
: 데이터 결과물을 합쳐서 한번에 보고 싶은 경우, union을 이용해 두 쿼리를 묶어준다.
※ 노란색과 파란색 박스의 필드명이 같아야 함!
(select '7월' as month, c1.title, c2.week, count(*) as cnt from courses c1
inner join checkins c2 on c1.course_id = c2.course_id
inner join orders o on c2.user_id = o.user_id
where o.created_at < '2020-08-01'
group by c1.title, c2.week
order by c1.title, c2.week)
UNION ALL
(select '8월' as month, c1.title, c2.week, count(*) as cnt from courses c1
inner join checkins c2 on c1.course_id = c2.course_id
inner join orders o on c2.user_id = o.user_id
where o.created_at >= '2020-08-01'
group by c1.title, c2.week
order by c1.title, c2.week)
order by month, title, week;
결과 👉
union
을 사용하면 내부 정렬이 먹지 않는다!
: enrolled_id별 수강완료(done=1)한 강의 갯수를 세어보고, 완료한 강의 수가 많은 순서대로 정렬해보기. user_id도 같이 출력되어야 한다.
SELECT e.enrolled_id, e.user_id, COUNT(*) as e_cnt
FROM enrolleds e
INNER JOIN enrolleds_detail ed
ON e.enrolled_id = ed.enrolled_id
WHERE ed.done = 1
GROUP BY e.enrolled_id, e.user_id
ORDER BY e_cnt DESC ;
결과 👉
subquery
를 사용하도록 한다.count
는 NULL을 세지 않는다.콤마(,)
로 group by, order by를 두 개 이상 필드로 조건을 걸 수 있다.3주차는 이론보다 실습 위주로 작성을 했는데 확실히 쿼리를 직접 써보는 게 문법을 익히는데 빠르다. 에러가 자주 나는 구문 위주로 추가 작성해보기
(EX 8,9는 다시 풀어보는 문제였는데도 복잡하고 헷갈렸다. 최대한 정답 코드스니펫을 참고 하지 않도록 여러번 복습하고 다시 풀어보기)
join을 배우고 나니 error : ~is ambiguous
가 자주 뜬다. 문장 그대로 중의적이라는 뜻인데, 여러 테이블을 사용하고 각 테이블에서도 다른 테이블과 중복되는 필드가 있으니, 출력하고자 하는 데이터가 어떤 내용으로 사용하는지에 중점을 두어 연결해주어야 한다.
ex) 고객들의 정보를 가져와야 한다면, 고객 정보 중 가장 기본값이 되는 user_id
로 연결해준다.
조인해야 하는 테이블이 무엇인지, 조인하는 필드가 무엇인지 잘 선별하도록 해야 한다. 어디에 어떤 데이터가 있는지 기억이 안나면 속성값을 일일이 클릭하기 번거로웠는데, 엔터티 관계도를 켜두니 쿼리 작성을 시작할 때 훨씬 직관적이라 수월했다.