[SQLD 8화] INNER JOIN의 기본 원리와 데이터 중복(Duplication) 방지 실무 가이드 (SQLD, 직장인 SQL)

1. 단일 테이블을 넘어 다차원 분석으로: JOIN의 서막

7화에서 우리는 데이터의 조건에 따라 흐름을 분기하고 쿼리 내에서 유연하게 데이터를 분류해 주는 CASE WHEN 조건문을 활용해 고객 세그먼트를 정의하는 방법을 살펴보았습니다.

지금까지 1화부터 7화에 이르기까지 다루었던 내용들은 단 하나의 테이블 안에서 데이터를 필터링하고 변환하는 ‘단일 테이블 제어 기법’이 주를 이루었습니다. 그러나 기업의 실제 업무 환경에서는 내가 원하는 모든 데이터가 친절하게 단 하나의 테이블에 전부 모여 있는 경우가 극히 드뭅니다.

대형 카드사나 이커머스, 유통 기업 등의 데이터베이스는 효율적인 데이터 관리와 정돈을 위해 정보를 쪼개어 저장하는 구조를 취합니다. 예컨대 ‘고객의 이름과 가입일’은 회원 마스터 테이블에 있고, ‘고객이 구매한 상품과 금액’은 매출 상세 테이블에 각각 분리되어 존재하는 방식입니다.

이때 분리된 두 개 이상의 테이블을 연결고리(공통 칼럼)를 기반으로 결합하여 하나의 거대한 분석 판을 짜주는 SQL의 핵심 기능이 바로 조인(JOIN)입니다. 그중에서도 가장 기본이 되며 양쪽 테이블에 공통으로 존재하는 교집합 데이터만을 정밀하게 추출해 내는 INNER JOIN (내부 조인)의 문법 규칙과 실무 주의점을 본 연작 시리즈의 기준인 테라데이터 SQL 환경을 바탕으로 설명하겠습니다.

2. INNER JOIN의 기본 문법 구조와 메커니즘

INNER JOIN은 연결하고자 하는 두 테이블 사이에서 공통된 값을 가지는 key 칼럼(일명 연결고리)을 비교하여, 양쪽 테이블 모두에 데이터가 존재하는 행(Row)들만 추출하는 합쳐보기 방식입니다. 만약 한쪽 테이블에는 존재하지만 다른 쪽 테이블에는 없는 데이터라면 최종 결과 집합(Result Set)에서 제외되는 특성을 가집니다.

-- INNER JOIN의 가장 표준적인 문법 구조
SELECT 
    A.칼럼명1
  , A.칼럼명2
  , B.칼럼명3
FROM 
    테이블명1 A
INNER JOIN 
    테이블명2 B
ON 
    A.공통키_칼럼 = B.공통키_칼럼;

💡 실무 작성 시 기억해야 할 가독성 규칙

  • 테이블 앨리어스(Alias, 별칭)의 활용: 조인을 진행할 때는 FROM 절과 INNER JOIN 절 뒤에 명시한 테이블명 뒤에 AB 같은 가벼운 이니셜(별칭)을 붙여주는 것이 일반적입니다. 이를 지정해 두면 SELECT 절이나 ON 절에서 긴 테이블명을 일일이 대입하지 않고 A.CUST_NO 형태로 간결하게 기술할 수 있어 쿼리의 가독성을 높이는 대안이 됩니다.
  • ON 절을 통한 연결고리 명시: ON 키워드 뒤에는 두 테이블을 연결할 매칭 기준 칼럼을 등호(=) 연산자로 연결합니다. 이 기준이 명확해야 시스템이 엉뚱한 데이터를 엮지 않고 정확한 매칭을 수행할 수 있습니다.

3. 현업 비즈니스 사례로 보는 데이터 중복(Duplication) 대참사

INNER JOIN 문법 자체는 직관적이기 때문에 쉽게 다가오지만, 실무에서 분석가들이 가장 많이 소리 없이 무너지는 구간이기도 합니다. 바로 조인 연산 도중 나도 모르게 데이터가 복사되어 늘어나는 ‘데이터 뻥튀기(Duplication)’ 현상 때문입니다. 이해를 돕기 위해 실제 현업에서 빈번하게 일어나는 가상의 비즈니스 시나리오를 살펴보겠습니다.

📊 [실무 사례 예시: 마케팅 팀 최 선임의 매출 집계 오류]

의류 브랜드 데이터 분석가인 최 선임은 기획 부서로부터 “회원 마스터 정보와 이번 달 특별 기획전 매출 정보를 결합하여, 기획전 상품을 구매한 고객들의 회원번호, 등급, 그리고 구매금액을 결합한 리포트를 추출해 달라”는 요청을 받았습니다.

최 선임은 회원 기본 마트(CUSTOMER_MSTR)와 기획전 구매 상세 마트(PROMO_PURCHASE_DTL)를 CUST_NO(회원번호)를 기준으로 엮으면 되겠다고 판단하여 다음과 같이 INNER JOIN 쿼리를 실행했습니다.

SQL

-- [주의] 데이터 뻥튀기(Duplication) 리스크를 인지하지 못한 위험한 조인 쿼리
SELECT 
    A.CUST_NO
  , A.CUST_GRADE_CD
  , B.PURCHASE_AMT
FROM 
    DW_MART.CUSTOMER_MSTR A          -- 회원 마스터 테이블
INNER JOIN 
    DW_MART.PROMO_PURCHASE_DTL B     -- 기획전 구매 상세 테이블
ON 
    A.CUST_NO = B.CUST_NO;

🚨 무엇이 문제였을까? (일대다 조인의 역습)

최 선임이 도출한 최종 리포트의 전체 합계 금액은 기획전 총매출액보다 수배 이상 비정상적으로 크게 집계되었습니다. 원인을 조사해 보니, 결합 대상이었던 회원 마스터 테이블(CUSTOMER_MSTR)의 데이터 적재 형태를 오해한 결과였습니다.

최 선임은 회원 마스터 테이블에 회원번호(CUST_NO)별로 단 1행씩만 존재할 것이라고 팩트를 가정했습니다. 하지만 해당 테이블은 고객이 등급 변동을 겪을 때마다 이력이 누적되는 이력 관리형 테이블이었기에, 우량 고객인 ‘김 회원’의 회원번호 데이터가 테이블 내에 과거 이력까지 포함하여 총 3행(Row) 적재되어 있었습니다.

  • 회원 마스터 테이블(A): 김 회원 데이터 3행 존재 (과거 등급 포함)
  • 기획전 구매 상세(B): 김 회원의 구매 건수 2행 존재

이 상태에서 단순 INNER JOIN을 수행하면, 데이터베이스 엔진은 매칭되는 모든 경우의 수를 조합하여 연결합니다. 결과적으로 김 회원의 구매 내역 2건이 각각 마스터의 3행과 전부 결합하면서 최종 결과 화면에는 3 x 2 = 6, 총 6행의 데이터가 생성되는 오류가 발생한 것입니다. 이로 인해 김 회원이 실제 지출한 금액이 3배로 뻥튀기되어 전사 실적 보고서의 신뢰성이 완전히 무너지는 비즈니스 리스크로 번질 수 있습니다.

4. 데이터 중복을 차단하는 현직자들의 조인 제어 치트키

이러한 조인 도중의 데이터 왜곡을 방지하고 데이터 무결성을 유지하기 위해, 실무 쿼리 빌드 시 반드시 적용해야 하는 핵심 방어 전략 두 가지를 제안합니다.

1) 조인 전 기준 테이블의 고유성(Uniqueness) 확보

조인을 실행하기 전, 매칭하고자 하는 key 칼럼이 한쪽 테이블에서 ‘단 한 건만 존재하는 유일한 상태(1:N 구조에서 1의 역할)’인지 사전 검증하거나 수동으로 압축해 주는 작업이 대안으로 요구됩니다. 위 최 선임의 사례의 경우, 회원 마스터 테이블에서 가장 최신의 등급 데이터 1건만 남기도록 필터 조건을 펜싱해 주어야 합니다.

SQL

-- [올바른 예시 1] 마스터 테이블의 이력 조건을 제한하여 1:N 조인 구조를 완성한 안전한 쿼리
SELECT 
    A.CUST_NO
  , A.CUST_GRADE_CD
  , B.PURCHASE_AMT
FROM 
    DW_MART.CUSTOMER_MSTR A
INNER JOIN 
    DW_MART.PROMO_PURCHASE_DTL B
ON 
    A.CUST_NO = B.CUST_NO
WHERE 
    A.END_DT = '9999-12-31'; -- 테라데이터 등에서 현재 유효한 최신 이력만 거르는 일반적인 실무 조건

A.END_DT = '9999-12-31'과 같은 현재 진행형 이력 필터를 WHERE 절에 결합해 주면, 과거 이력들이 제거되면서 회원 마스터 테이블 내 김 회원의 데이터는 최신 이력 1건만 남게 됩니다. 따라서 구매 상세 테이블의 2건과 깨끗하게 일대다(1:N) 매칭이 이루어지므로 데이터 뻥튀기 현상을 완벽히 차단할 수 있게 됩니다.

2) GROUP BY를 통한 집계 후 조인 전략

만약 상세 구매 내역의 건별 나열이 목적이 아니라 ‘고객별 총 구매 금액’을 구하는 요약 보고서가 목적이라면, 조인을 맺기 전에 구매 상세 테이블 자체를 고객별로 미리 합산(Roll-up)하여 행 수를 줄여놓은 뒤 조인을 수행하는 방식이 시스템 자원을 절약하고 계산 오류를 방지하는 훌륭한 치트키로 평가받습니다. 이 구조는 추후 연재될 인라인 뷰(Inline View) 편에서 더욱 심도 있게 응용될 예정입니다.

5. 팩트 체크 및 데이터 분석가의 관점 점검

기술 콘텐츠의 정확성을 확보하기 위해, 실무에서 INNER JOIN을 수행할 때 많은 초보 직장인이 헷갈려하는 사실 관계들을 명확히 점검해 보겠습니다.

  • ON 절과 WHERE 절의 필터링은 완전히 동일하게 작동하는가?
    • 팩트 체크: INNER JOIN 환경에 한해서만 보면, 필터 조건을 ON 절에 적든 WHERE 절에 적든 데이터베이스 옵티마이저가 내부적으로 쿼리를 최적화하여 동일한 결과 집합과 유사한 속도를 도출해 내는 경우가 많습니다. 그러나 대칭적인 구조를 지닌 LEFT OUTER JOIN(외부 조인) 환경에서는 조건을 ON에 쓰느냐 WHERE에 쓰느냐에 따라 아예 추출되는 데이터의 모수 자체가 달라지는 거대한 문법적 차이가 발생합니다. 따라서 장기적인 쿼리 안정성과 표준 가독성을 확보하기 위해서는 두 테이블의 뼈대를 엮어주는 연결고리는 명확히 ON 절에 기술하고, 결합이 끝난 집합에 대한 순수 필터링 조건은 WHERE 절에 격리하여 기술하는 분리 작성법이 실무 관점에서 안전한 표준으로 권장됩니다.

6. 8화 요약 및 실무 다중 테이블 조인 체크리스트

쿼리를 최종 빌드하여 실행 환경에 던지기 전, 데이터 누락이나 중복의 결점이 없는지 진단할 수 있는 요약 가이드를 제공합니다.

체크포인트발생 가능한 실무 리스크현직 분석가의 권장 대안
조인 Key 칼럼의 다중 이력 존재 여부1:1 매칭인 줄 알았으나 M:N으로 엮여 데이터가 곱셈 형태로 증가조인 전 기준 테이블의 Key값으로 COUNT 연산을 수행하여 중복 여부 확인
양쪽 테이블 데이터 타입 일치 여부한쪽은 문자형 ID, 한쪽은 숫자형 ID형태일 때 조인 실패 오류 발생ON A.ID = CAST(B.ID AS VARCHAR(10)) 등 데이터 타입을 일치시키는 변환 함수 매칭
결합 대상 테이블의 스케일 체급억 단위 테이블끼리 무조건 조인 시 연산 지연 유발조인하기 전 WHERE 절에서 각각의 테이블 모수를 최대한 압축한 뒤 결합 유도

7. 결론 및 다음 화 예고

이번 8화에서는 다중 테이블 데이터 가공의 핵심 시작점인 INNER JOIN의 문법 구조를 살펴보고, 실무 데이터 집계 시 가장 빈번하고 치명적으로 발생하는 데이터 중복(뻥튀기) 현상의 메커니즘과 이를 제어하기 위한 고유성 확보 전략을 다각도로 점검해 보았습니다. 분리된 데이터를 유기적으로 결합하여 새로운 분석 가치를 도출하는 역량은 뛰어난 SQL 데이터 분석가로 성장하기 위한 가장 중요한 핵심 역량 중 하나입니다.

이어지는 9화에서는 INNER JOIN과 대비되는 개념이자 현업 마케팅 타겟팅 및 실적 매칭의 동반자인 LEFT OUTER JOIN (외부 조인)의 세계를 집중 분석합니다. 두 테이블을 결합할 때, 한쪽에만 데이터가 있고 다른 쪽에는 데이터가 없는 ‘누락 거래 건’들을 버리지 않고 살려내어 기준 테이블 마스터 정보를 온전히 보존하는 방법과, 외부 조인 과정에서 발생하는 빈칸(NULL) 데이터들을 6화에서 배웠던 결측치 처리 함수와 결합하여 세련된 대시보드 원천 데이터를 빌드하는 실무 치트키를 상세히 파악해 보겠습니다.

댓글 남기기