개요
실무 대부분의 케이스는 내부 조인(INNER JOIN)으로 해결된다. 하지만 "당일 출근 기록은 없지만 직원 목록은 모두 출력"되었으면 좋겠다는 화면 구현이 종종 요구된다. 이런 경우는 (1) 앱 계층에서 쿼리를 분리하거나, 백엔드 페이징 처리가 필요한 경우는 (2) 외부 조인(OUTER JOIN)으로 처리하게 된다.
외부 조인 중 LEFT JOIN(이하 왼쪽 조인)은 특히나 첫 회사의 첫 실무를 할때 어려움을 많이 느꼈던 개념이었다. 왼쪽 조인을 최근 김영한님 강의를 보면서 정리하게 되었는데 (1)거기서 학습한 내용과 내가 실제로 (2)회사에서 어떤것을 느끼고 사용해왔는지를 정리해둔다.
왼쪽 외부 조인만 주로 사용한다
실무에서는 왼쪽 외부 조인을 주로 사용한다. 그 이유는 아래 그림을 통해 이해할 수 있다. 아래 그림은 왼쪽 조인과 오른쪽 조인을 비교한 이미지다(참고로 공간 부족으로 일부만 보여주고 있다).


FROM 절과 JOIN절의 테이블만 바꿔주면 동일한 결과를 얻을 수 있고, 번갈아 사용하면 오히려 쿼리 가독성이나 직관성이 떨어진다고 느꼈기 때문이다. 김영한님은 강의에서 아래와 같은 의견을 주장했는데 동의한다.
실무에서는 LEFT JOIN이 RIGHT JOIN보다 훨씬 더 많이 사용된다. 보통 분석의 기준이 되는 테이블을 FROM 절에 먼저 쓰고, 필요한 정보를 담은 다른 테이블들을 LEFT JOIN으로 하나씩 붙여나가는 방식으로 쿼리를 작성하는 것이 더 직관적이기 때문이다. RIGHT JOIN은 테이블의 순서를 바꾸면 언제나 LEFT JOIN으로 동일하게 표현할 수 있다.
왼쪽은 JOIN절을 기준으로
왼쪽 조인이 익숙하지 않을때 많이 헷갈렸던 부분이다. LEFT JOIN 다음에 나오는 테이블은 오른쪽이며 FROM 절 혹은 앞에서 계속 조인된 결과 테이블이 왼쪽에 해당된다. 아래에서 users 테이블이 왼쪽 테이블이 되며, orders 테이블이 오른쪽 테이블이 된다. 즉, 왼쪽 조인의 결과 users 테이블의 레코드는 조인될 오른쪽 레코드가 없어도 유지(Retained)된다. 참고로 논리적으로는 이렇지만 실제로 옵티마이저의 실행계획을 보면 드라이빙 테이블과 드리븐 테이블을 선택은 '비용 기반 최적화'를 따르기 때문에 다를 수 있기 때문에 그 둘을 헷갈려서는 안된다.

실무에서 사용되는 방식 두 가지를 언급할게 있다. 첫 번째로 on절은 괄호()로 묶어서 하나의 논리 단위로 만든다. 이게 결과적으로 차이를 내지는 않지만 가독성과 컨벤션으로 보통 이렇게 작성하는 편이다. 물론 필수는 아니다.
두 번째로 on 절에서 두 테이블을 조인하는 키(Join Key) 컬럼들은 아래 처럼 순서를 맞춰주면 가독성이 좋아진다. 논리적인 관점에서 왼쪽에는 정말 왼쪽 테이블을 두고 오른쪽에는 오른쪽 테이블을 둔다. 이것도 필수는 아니다.

WHERE 절에서 왼쪽 조인의 컬럼 사용은 '안티패턴'
'안티패턴' 이라고 자주 말한다. 어떤 케이스인지 아래서 살펴본다 :


왼쪽 그림에서 왼쪽 조인을 사용했음에도 불구하고, 주문 테이블 관련 where 조건을 넣어두었다. 어쩌다 한 번씩 이렇게 작성하는 경우가 있는데 논리적으로 맞지 않는 방식이다. 왜냐하면 왼쪽 조인 자체가 조인 결과가 없으면 null로 채워서 붙이는 결과를 얻도록 하는 목적이기 때문이다. 사실상 내부 조인을 하면 되는 상황인데, 보통은 옵티마이저가 내부조인으로 바꿔서(Outer join elimination) 실행하게 된다. 아래는 두 조인의 실행 계획(Explain Analyze)의 결과이다.


만약 옵티마이저가 복잡한 쿼리로 인해 최적화를 실패하면 왼쪽 조인 이후 필터링을 하는 두 단계 과정을 거치게된다. 물론 성능상 큰 차이가 없고 왼쪽 조인이 가독성이나 편리함때문에 선택하는 경우도 종종 있다. 만약 데이터 수도 적고 성능이슈가 없지만 왼쪽 조인을 선택함으로써 유지보수가 편리하다면 선택하게 된다. 득과 실을 고려하기만 하면 된다.
레퍼런스
- 김영한의 실전 데이터베이스 - 기본편 | 김영한 (2025, 인프런)