0. 들어가며


SQL을 작성하다 보면 "JOIN을 할 때, 왼쪽 테이블의 컬럼 값을 오른쪽 서브쿼리 안에서 변수처럼 쓰고 싶다"는 생각이 들때가 있다. 그리고 일반적인 JOIN에서는 불가능한 이 작업을 가능하게 해주는 것이 바로 LATERAL JOIN이다.

LATERAL JOIN은 SQL 표준(SQL:1999)에 도입된 기능으로, 서브쿼리가 자신보다 먼저 명시된 선행 테이블의 컬럼을 참조할 수 있도록 허용하는 조인 방식이다.

일반적인 서브쿼리는 독립적으로 실행되어 그 결과를 테이블에 붙이는 방식이지만, LATERAL JOIN은 마치 프로그래밍의 for-each 루프처럼 작동한다. 왼쪽 테이블의 각 행(row)에 대해 오른쪽 서브쿼리를 반복해서 실행하는 구조라고 이해하면 쉽다.

예를 들어, 왼쪽 테이블에 5명의 사용자가 있고, 오른쪽 서브쿼리에서 사용자별 최근 주문 내역을 3개씩 가져오려 한다면

  • 왼쪽(사용자 1) -> 오른쪽(주문 내역 조회 쿼리 1회 실행, 최근 주문 내역 3개를 가져옴)
  • 왼쪽(사용자 2) -> 오른쪽(주문 내역 조회 쿼리 1회 실행, 최근 주문 내역 3개를 가져옴)
  • ...
  • 왼쪽(사용자 5) -> 오른쪽(주문 내역 조회 쿼리 1회 실행, 최근 주문 내역 3개를 가져옴)

이렇게 총 5번의 하위 SELECT 실행이 내부적으로 반복된다.

1. 왜 'LATERAL' 인가?


'Lateral'은 '측면의', '옆으로의'라는 뜻을 가지고 있다. 이는 서브쿼리가 자기 울타리 안에 갇혀 있지 않고, 옆에 있는(Lateral) 테이블의 데이터를 들여다보고 참조할 수 있다는 의미를 담고 있다.

일반 JOIN/서브쿼리와 LATERAL JOIN의 결정적인 차이는 '상호작용성'에 있다.

구분 일반 JOIN / 서브쿼리 LATERAL JOIN
참조 범위 서브쿼리 외부의 컬럼의 참조할 수 없음 왼쪽 테이블의 컬럼을 자유롭게 참조 가능
실행 방식 서브쿼리가 한 번 실행되어 결과셋 생성 왼쪽 행마다 서브쿼리가 반복 계산됨
주요 용도 단순 데이터 결합 행별 계산, TOP-N 추출, 복잡한 가공

따라서 LATERAL JOIN은 다음과 같은 경우에 유용하게 사용할 수 있다.

  • 행별로 상위 N개의 결과를 가져와야 할 때 (예: 각 사용자별 최신 주문 3건)
  • 계산된 결과를 즉시 재사용해야 할 때 (SELECT 절의 복잡한 연산을 반복하지 않음)
  • 집합 리턴 함수(Set-returning functions)를 사용할 때 (JSON 데이터 풀기 등)
PostgreSQL에서는 LATERAL 키워드를 명시하며, Oracle에서는 CROSS APPLY 또는 OUTER APPLY라는 이름으로 동일한 기능을 제공한다.

2. LATERAL JOIN의 문법과 작동 원리


LATERAL JOIN이 강력한 이유는 그 동작 방식에 있다. 문법 자체는 간단하지만, 데이터가 흐르는 방식을 이해하는 것이 중요하다.

2-1. 기본 문법

PostgreSQL 기준으로 문법은 다음과 같다. JOIN 키워드 뒤에 LATERAL을 붙이고 서브쿼리를 작성한다.

SELECT *
FROM table_a a
CROSS JOIN LATERAL (
    SELECT *
    FROM table_b b
    WHERE b.foreign_id = a.id -- a(왼쪽 테이블)의 컬럼을 참조!
) sub;

2-2. 동작 원리 (The For-Each Loop)

일반적인 JOIN은 두 테이블을 한꺼번에 비교하여 합치지만, LATERAL JOIN은 다음과 같은 순서로 움직인다.

  1. Step 1: 왼쪽 테이블(table_a)에서 첫 번째 행을 가져온다.
  2. Step 2: 그 행의 값을 서브쿼리의 파라미터로 전달한다.
  3. Step 3: 서브쿼리를 실행하여 결과 행들을 생성한다.
  4. Step 4: 왼쪽 테이블의 다음 행으로 넘어가서 위 과정을 반복한다.

3. 실전 예제: "각 카테고리별 최신 상품 2개씩 가져오기"


LATERAL JOIN이 가장 빛나는 순간은 "그룹별로 상위 N개의 결과만 필터링"해야 할 때이다.

3-1. 문제 상황

categories 테이블과 products 테이블이 있을 때, 각 카테고리별로 가장 비싼 상품 2개씩만 뽑아보고 싶다고 가정해보자. LATERAL JOIN이 없다면 보통 ROW_NUMBER()를 사용하여 전체 순위를 매긴 뒤, 서브쿼리로 한 번 더 감싸서 필터링해야 한다. 이는 쿼리가 다소 길고 직관적이지 않다.

SELECT category_name, product_name, price
FROM (
    SELECT 
        c.name AS category_name,
        p.name AS product_name,
        p.price,
        ROW_NUMBER() OVER (PARTITION BY p.category_id ORDER BY p.price DESC) as rank
    FROM categories c
    JOIN products p ON c.id = p.category_id
) ranked_products
WHERE rank <= 2; -- 서브쿼리 밖에서 다시 필터링 필요

3-2. LATERAL JOIN을 이용한 해결

LATERAL을 사용하면 서브쿼리 안에서 ORDER BY와 LIMIT을 자유롭게 사용할 수 있어 코드가 훨씬 간결해진다. 마치 카테고리 하나하나를 순회하며 "그 카테고리의 비싼 상품 2개만 가져와"라고 명령하는 것과 같다.

SELECT 
    c.name AS category_name,
    p.name AS product_name,
    p.price
FROM categories c
LEFT JOIN LATERAL (
    SELECT name, price
    FROM products
    WHERE category_id = c.id  -- 외부 테이블(c) 참조
    ORDER BY price DESC       -- 가격순 정렬
    LIMIT 2                   -- 카테고리당 딱 2개만!
) p ON TRUE;

왜 LATERAL JOIN을 사용하는게 더 좋을까?

  • 가독성: 윈도우 함수(ROW_NUMBER())와 서브쿼리를 중첩해서 쓰는 것보다 논리 구조가 훨씬 명확하다.
  • 유연성: 서브쿼리 내부에서 복잡한 연산이나 함수 호출을 행 단위로 수행할 수 있다.
  • 성능: 인덱스가 적절히 설정되어 있다면, 모든 데이터를 읽어서 순위를 매기는 윈도우 함수보다 각 카테고리별로 필요한 데이터만 인덱스로 찾아내는 LATERAL JOIN이 더 빠를 수 있다.

3-3. 주의사항

  • 참조의 방향성: 반드시 왼쪽(먼저 정의된) 테이블의 컬럼만 오른쪽(LATERAL)에서 참조할 수 있다. 반대 방향은 불가능하다.
  • 성능 체크: 데이터 양이 많을 경우 왼쪽 테이블의 행 수만큼 서브쿼리가 호출되므로, 서브쿼리 내부의 조인 조건에 인덱스가 걸려 있는지 반드시 확인해야 한다.

4. 마치며


지금까지 LATERAL JOIN의 정의부터 실전 예제까지 살펴보았다. 처음 접하면 다소 낯설 수 있지만, 한 번 익숙해지면 SQL의 표현력을 비약적으로 높여주는 도구이다.

핵심 요약

  • 동적 참조: 서브쿼리가 외부 테이블의 컬럼을 실시간으로 참조할 수 있게 해준다.
  • 강력한 행별 연산: TOP-N 추출, JSON 파싱, 복잡한 수식 계산 등 '행 단위' 작업에 최적화되어 있다.
  • 코드 가독성: 윈도우 함수와 중첩 서브쿼리로 복잡하게 얽힌 코드를 훨씬 직관적인 for-each 구조로 변경해 준다.

선택을 위한 가이드

모든 상황에서 LATERAL JOIN이 정답은 아니다. 상황에 맞춰 적절한 도구를 선택해야 한다.

  1. 단순 조인이라면? → 일반 JOIN이 가장 빠르고 표준적이다.
  2. 전체 순위나 누적 합이 필요하다면? → 윈도우 함수(OVER())가 효율적이다.
  3. 그룹별로 일부 행만 추출하거나, 행마다 복잡한 서브쿼리 결과를 붙여야 한다면? → 고민하지 말고 LATERAL JOIN을 선택하자.

데이터베이스의 성능과 쿼리의 가독성 사이에서 고민하는 개발자에게 LATERAL JOIN은 훌륭한 해답이 된다. 특히 PostgreSQL이나 Oracle(Apply), SQL Server 환경에서 작업하고 있다면, LATERAL JOIN을 사용했을 때 복잡했던 SQL 코드가 한결 가벼워지는 경험을 할 수 있을 것이다.

 

'TIL' 카테고리의 다른 글

데이터베이스의 Git, Flyway 도입기  (0) 2026.01.19
왜 jOOQ인가  (0) 2026.01.09

+ Recent posts