본 포스팅의 예제 쿼리는 MySQL 기준입니다.
SQL을 사용하면서 join은 필수적이라고 볼 수 있다.
원하는 결과를 얻기 위해선 여러 테이블의 조합이 필요하기 때문에 join이 항상 필요하다.
그 중에서도 많이 쓰이는 join 종류를 꼽자면 inner join과 left join 인 것 같다.
이번 포스팅에서는 left join에 대해 다뤄볼 예정이다. 간단하게 left join에 대해서와 언제 쓰일 수 있는 지, 그리고 주의해야할 점을 언급하며 실습을 통해 주의점을 소개할 것이다.
그동안 나는 left join을 쓸 일을 많이 겪어본 것은 아니지만 쓸 때는 테이블의 순서를 딱히 고려하지 않았었다.
근데 left join 시 테이블의 순서에 따라 결과, 성능이 달라질 수 있다는 글들을 접했고 이에 대해 직접 실습해보았다.
먼저 left join에 대해 간략히 살펴보자.
LEFT JOIN이란 쉽게 생각하면 왼쪽 테이블이 주인공이 되는 조인이다.
다음 그림을 보며 쉽게 이해해보자!
예시 이미지에는 테이블이 두개가 있다. left join을 하게 되면 이미지의 left table이 주인공이 된다.
이미지를 보면 왼쪽 테이블은 모든 레코드를 반환하지만 오른쪽 테이블은 왼쪽 테이블과 일치되는 값만 반환된다.
즉, 왼쪽 테이블이 주인공이 되어 주인공에게 모두 맞춰주는 것이다. 왼쪽 테이블의 CountryId의 4라는 값이 실제로 오른쪽 테이블에는 없지만 왼쪽 테이블이 주인공이기 때문에 결과로는 반환이 되고 오른쪽 테이블의 값이 null로 반환된다.
그렇다면 LEFT JOIN을 언제 활용할 수 있을까?
예를 들어 Player라는 테이블과 Team이라는 테이블이 있다고 해보자.
Player가 속한 Team을 조회하고 싶을 때 inner join을 통해 결과를 반환 받을 수 있다.
하지만 속해있는 Team이 없는 Player까지 포함하여 모두 조회하고 싶을 때는 Player 테이블이 주인공이 되는 LEFT JOIN을 활용하여 결과를 반환 받을 수 있다. 그러면 속해있는 Team이 없는 Player의 Team 필드는 null로 표시되어 반환받을 수 있다.
first_name | last_name | phone_number | team_nmae |
---|---|---|---|
injun | choi | 010-3434-2323 | Liverpool |
younghee | kim | 010-5454-3434 | Liverpool |
chulsu | lee | 010-1212-1212 | Tottenham |
minsu | park | 010-6565-6565 | Tottenham |
son | lim | 010-9898-9898 | null |
LEFT JOIN은 많이 쓰이는 만큼 주의해야 할 점을 아는 것도 중요하다.
실제 쿼리를 날려보며 볼 것이기 때문에 예제 테이블을 준비해두었다.
team
id | team_nmae |
---|---|
1 | Liverpool |
2 | Tottenham |
player
player_id | position_id | team_id | first_name | last_name | phone_number |
---|---|---|---|---|---|
1 | 1 | 1 | injun | choi | 010-3434-2323 |
2 | 2 | 1 | younghee | kim | 010-5454-3434 |
3 | null | 2 | chulsu | lee | 010-1212-1212 |
4 | 2 | 2 | minsu | park | 010-6565-6565 |
5 | null | null | son | lim | 010-9898-9898 |
position
id | position_name | prefer_rate |
---|---|---|
1 | ST | 3 |
2 | CB | 5 |
3 | CM | 1 |
4 | LW | 2 |
5 | RB | 4 |
주의해야 할 점은 크게 두가지를 소개할 것이다.
첫번째로는 SELECT문에 가장 많은 열을 가져와야 하는 테이블을 우선적으로 명시해야한다❗
예시 테이블을 보고 쿼리를 날려보며 보도록 하겠다.
가정한 상황은 다음과 같다.
첫번째 쿼리는 주의점을 지키며 작성해보자. selelct문에 player의 컬럼이 제일 많이 들어가므로 player테이블을 먼저 명시해준다.
select p.first_name, p.last_name, p.phone_number, t.team_name, po.position_name
from player as p
left join team as t on t.id = p.team_id
left join position as po on p.position_id = po.id;
이에 대한 결과는 다음과 같다.
first_name | last_name | phone_number | team_name | position_name |
---|---|---|---|---|
injun | choi | 010-3434-2323 | Liverpool | ST |
younghee | kim | 010-5454-3434 | Liverpool | CB |
chulsu | lee | 010-1212-1212 | Tottenham | null |
minsu | park | 010-6565-6565 | Tottenham | CB |
son | lim | 010-9898-9898 | null | null |
원하는 상황대로 선수가 모두 조회되었고 팀이 없거나 포지션이 없는 선수는 해당 컬럼이 null로 반환되었다.
그럼 주의점을 어기고 team 테이블을 먼저 적어주면 어떻게 될까.. 실행해보자.
select p.first_name, p.last_name, p.phone_number, t.team_name, po.position_name
from team as t
left join player as p on t.id = p.team_id
left join position as po on p.position_id = po.id;
from 절을 보면 player가 아닌 team이 들어가있다. 결과는 다음과 같다.
first_name | last_name | phone_number | team_name | position_name |
---|---|---|---|---|
younghee | kim | 010-5454-3434 | Liverpool | CB |
injun | choi | 010-3434-2323 | Liverpool | ST |
minsu | park | 010-6565-6565 | Tottenham | CB |
chulsu | lee | 010-1212-1212 | Tottenham | null |
뭔가 다르다. 위의 테이블은 5개의 레코드를 반환받았는데 이번엔 4개의 레코드만 반환되었다.
LEFT JOIN의 특성대로 team 테이블이 주인공이 되어서 team에 초점이 맞춰진 것이다.
그렇기에 주인공이 아닌 Player 테이블의 속해있는 team이 없는 레코드는 반환되어지지 않았다🥲
가정된 상황 의도대로 이루어지지 않은 것이다.
이렇게 어떤 테이블이 먼저 오느냐에 따라 LEFT JOIN에서는 다른 결과를 받는다.
INNER JOIN이라면 어떨까? 위 두 쿼리가 둘다 INNER JOIN이라면 둘의 쿼리는 결과가 동일하다.
INNER JOIN은 두 테이블의 일치하는 값에 대해서만 결과가 반환되기 때문이다.
LEFT JOIN을 사용하게 된다면 위에서 본 것 처럼 INNER JOIN을 사용할 때와 마냥 비슷하게 생각하지는 말자.
테이블의 순서에 따라 결과가 달라질 수 있으니 고려사항이 추가된 것이다.
두번째로 주의해야 할 점은 LEFT JOIN 이후에 INNER JOIN이 나오면 안된다는 것이다❗
사실 이는 어찌보면 당연한 것이다.
LEFT JOIN을 하는 이유를 생각해보자. 여러 테이블이 있을 때 테이블 간 일치하는 값이 있든 없든 주인공 테이블 기준으로 모두 가져오려함이 목적이다.
근데 중간에 테이블간 일치하는 값만 반환하는 INNER JOIN을 사용한다는 것 자체가 LEFT JOIN을 활용하는 의미가 없어진다.
예시를 보자. 위의 예제쿼리에서 첫번째 쿼리는 가정한 상황 의도대로 결과가 잘 반환되었다. 그 쿼리에서 Position을 join 하는 부분을 기존 LEFT JOIN 방식에서 INNER JOIN으로 바꾸고 쿼리를 날려보겠다.
select p.first_name, p.last_name, p.phone_number, t.team_name, po.position_name
from player as p
left join team as t on t.id = p.team_id
**inner join** position as po on p.position_id = po.id
이 쿼리에 대한 결과를 join 별로 나누어 먼저 생각해보자.
먼저 player와 team을 LEFT JOIN한다. player의 원하는 컬럼이 조회되고 속해있는 팀이 있다면 팀 이름까지 반환되고 속해있는 팀이 없다면 해당 값이 null로 반환될 것이다. 여기까지의 결과는 다음과 같다.
first_name | last_name | phone_number | team_name |
---|---|---|---|
injun | choi | 010-3434-2323 | Liverpool |
younghee | kim | 010-5454-3434 | Liverpool |
chulsu | lee | 010-1212-1212 | Tottenham |
minsu | park | 010-6565-6565 | Tottenham |
son | lim | 010-9898-9898 | null |
그 다음 position에 대해 INNER JOIN이 실행된다. 그럼 배정받은 포지션이 없는 선수는 INNER JOIN 조건에
일치하지 않아 결과에서 제외된다. 여기서는 3번째 선수와 5번째 선수는 포지션이 없어 결과에 반환되지 않을 것이다.
그렇게 돼서 최종 결과는 다음과 같다.
first_name | last_name | phone_number | team_name | position_name |
---|---|---|---|---|
injun | choi | 010-3434-2323 | Liverpool | ST |
younghee | kim | 010-5454-3434 | Liverpool | CB |
minsu | park | 010-6565-6565 | Tottenham | CB |
위에서 단계별로 결과를 예측한대로 나오게 되었다.
이 결과를 반환받았다는 것은 이전에 수행한 LEFT JOIN 작업이 의미가 있는걸까? 라는 생각이 든다.
LEFT JOIN을 통해 수행된 작업이 INNER JOIN으로 인해 아무 의미가 없어졌다..😂
주의해야 할 점 두가지를 실습을 통해 봤으니 이해가 더 수월하다는 생각이 든다!
이번 포스팅에서의 핵심은 LEFT JOIN 사용 시 주의해야할 점이고 크게 두가지를 소개했다.
이 두가지를 실습을 통해 보았다.
LEFT JOIN은 INNER JOIN만큼이나 많이 쓰이기 때문에 주의해야할 점은 반드시 알아놓아야 하는 것 같다.
이번에는 쿼리에 따라 결과가 달라질 수도 있는 실습을 해보았는데 LEFT JOIN은 쿼리에 따라 성능이 달라지는 경우도 있다. 포스팅이 길어져 성능에 관한 얘기는 다음 포스팅으로 미뤄야겠다!