LEFT JOIN에서 조건은 왜 WHERE가 아니라 ON에 써야 할까?

최정우·2025년 12월 14일

DB

목록 보기
1/1

백엔드 개발을 하다 보면 이런 쿼리를 자주 작성하게 된다.

SELECT *
FROM users u
LEFT JOIN orders o
  ON o.user_id = u.id
 AND o.created_at >= '2024-01-01';

겉보기에는 단순한 LEFT JOIN 쿼리지만,
이 쿼리를 왜 이렇게 써야 하는지 정확히 설명할 수 있는 개발자는 생각보다 많지 않다.

이번 글에서는

  • LEFT JOIN의 동작 방식
  • ON 절과 WHERE 절의 역할 차이
  • 실무에서 자주 발생하는 오류 패턴
    아주 자세하게 정리해본다.

LEFT JOIN의 목적부터 다시 생각해보자

LEFT JOIN의 핵심 목적은 하나다.

기준 테이블의 row는 반드시 유지하고,
매칭되는 데이터가 있으면 붙인다.

위 쿼리에서 기준 테이블은 users다.

즉, 이 쿼리가 의도하는 바는 다음과 같다.

  • 모든 유저를 조회한다
  • 해당 유저의 주문 중
    2024-01-01 이후에 생성된 주문이 있으면 함께 조회한다
  • 주문이 없어도 유저는 결과에 포함된다

이 의도를 SQL로 정확히 표현한 것이 바로 위의 쿼리다.


JOIN은 “컬럼을 붙이는 작업”이 아니다

많은 개발자가 JOIN을 이렇게 오해한다.

JOIN = 컬럼을 옆에 붙이는 작업

하지만 실제로 JOIN은 row를 기준으로 새로운 결과 집합을 만드는 연산이다.

LEFT JOIN의 처리 순서를 단순화하면 다음과 같다.

  1. users 테이블의 row를 하나씩 읽는다
  2. orders 테이블에서 JOIN 조건에 맞는 row를 찾는다
  3. 조건에 맞는 row가 있으면 결합
  4. 없으면 orders 컬럼을 NULL로 채운 row 생성

여기서 중요한 점은
JOIN 조건은 ON 절에서만 판단된다는 것이다.


ON 절의 역할: “JOIN 대상 제한”

다시 쿼리를 보자.

LEFT JOIN orders o
  ON o.user_id = u.id
 AND o.created_at >= '2024-01-01';

이 조건은 다음을 의미한다.

  • orders.user_id = users.id 인 주문 중에서
  • created_at >= '2024-01-01' 인 row만
  • JOIN 대상으로 고려한다

즉, JOIN 자체에 참여할 orders row를 제한하는 조건이다.

이 조건에 맞는 주문이 없으면?

  • JOIN 실패
  • 하지만 LEFT JOIN이므로
  • users row는 유지되고
  • orders 컬럼은 NULL이 된다

이것이 LEFT JOIN의 본래 의미다.


왜 WHERE 절에 쓰면 안 될까?

많이 보게 되는 잘못된 쿼리는 다음과 같다.

SELECT *
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.created_at >= '2024-01-01';

겉으로 보면 거의 동일해 보이지만,
결과는 완전히 달라진다.

이유는 WHERE의 실행 시점 때문이다

SQL의 논리적 실행 순서는 단순화하면 다음과 같다.

  1. FROM
  2. JOIN (ON)
  3. WHERE
  4. SELECT

즉,

  • JOIN이 먼저 수행되고
  • WHERE는 JOIN 결과에 대해 필터링을 한다

LEFT JOIN 이후, 주문이 없는 유저의 row는 이렇게 생긴다.

users 컬럼: 값 있음
orders 컬럼: 전부 NULL

이 상태에서 WHERE 절을 적용하면?

WHERE o.created_at >= '2024-01-01'
  • o.created_at은 NULL
  • NULL >= '2024-01-01' → false
  • 해당 row는 제거된다

결과적으로:

  • 주문이 없는 유저는 모두 탈락
  • LEFT JOIN의 의미가 사라짐
  • INNER JOIN과 동일한 결과

ON vs WHERE 정리

이 차이를 명확하게 구분해야 한다.

ON 절

  • JOIN에 참여할 row를 제한
  • LEFT JOIN의 의미를 유지
  • “붙일 수 있으면 붙이고, 없으면 NULL”

WHERE 절

  • JOIN 결과 row를 필터링
  • NULL row도 제거 대상
  • LEFT JOIN을 INNER JOIN으로 바꿔버릴 수 있음

한 문장으로 요약하면:

LEFT JOIN에서 JOIN 대상에 대한 조건은 반드시 ON 절에 작성해야 한다.


실무에서 특히 중요한 이유

이 문제는 단순히 SQL 문법의 문제가 아니다.

실무에서는:

  • “유저는 전부 보여줘야 한다”
  • “주문은 특정 기간만 보고 싶다”
  • “데이터가 없는 경우도 UI에는 보여야 한다”

같은 요구사항이 매우 많다.

이때 ON / WHERE를 잘못 사용하면:

  • 유저 수가 줄어든다
  • 페이징 결과가 틀어진다
  • “데이터가 없어요”라는 버그가 발생한다

그리고 이런 버그는 DB를 모르면 찾기 어렵다.


결론

SELECT *
FROM users u
LEFT JOIN orders o
  ON o.user_id = u.id
 AND o.created_at >= '2024-01-01';

이 쿼리는 단순한 SQL이 아니라,
LEFT JOIN의 본질을 정확히 이해하고 작성한 쿼리다.

  • 기준 테이블은 users
  • orders는 조건에 맞는 경우에만 JOIN
  • 주문이 없어도 유저는 유지

이 차이를 이해하는 순간,
JOIN 결과가 왜 깨졌는지
왜 row가 사라졌는지
왜 DISTINCT를 써도 해결이 안 됐는지
설명할 수 있게 된다.

0개의 댓글