POWER QUERY · MERGE

병합했더니 행과 합계가
늘어났을 때

Left Outer 병합은 왼쪽 행마다 오른쪽의 모든 일치 레코드를 연결합니다. 행 증가가 오류인지 정상적인 one-to-many 관계인지는 보고서의 한 행이 무엇을 표현해야 하는지에 따라 달라집니다.

수정: · 파일 준비 없이 실행할 전체 쿼리와 실제 Excel 엔진 검증 결과를 추가했습니다.

01 / WHY

병합은 한 건만
골라 주지 않습니다

판매 테이블에 고객 C10의 판매가 두 건 있고 주소 테이블에 C10의 유효한 주소가 두 건 있다면, 고객 ID로 병합한 뒤 주소를 펼칠 때 각 판매가 주소 두 건과 결합됩니다. 판매 두 행이 네 행으로 늘어나는 것은 병합 엔진이 중복을 만든 것이 아니라 요청한 키로 가능한 모든 조합을 보여준 것입니다.

합성 예제에서 판매는 네 건이고 금액 합계는 42입니다. 주소 매치 수가 판매 순서대로 2, 2, 1, 0이라면 펼친 결과는 여섯 행이 됩니다. 반복된 판매 금액을 다시 더하면 72로 보입니다. 이때 72는 새 매출이 아니라 주소를 펼치면서 같은 판매가 반복된 결과입니다.

고객 C10의 S1과 S2가 각각 주소 A1·A2에 연결되어 두 판매의 금액이 30에서 60으로 반복됩니다. 나머지 판매를 포함한 전체 합계는 42에서 72로 보입니다.
그림 1. 같은 판매가 여러 주소와 연결되는 과정입니다. 본문의 합성 데이터를 그린 설명 이미지이며 Excel 스크린샷이 아닙니다. 이미지 크게 보기 (새 탭)
왼쪽 입력 · Sales (4행, 합계 42)
sale_idcustomer_idamount
S1C1010
S2C1020
S3C205
S4C307
오른쪽 입력 · Addresses (C10에 두 주소)
address_idcustomer_idcity
A1C10Seoul
A2C10Busan
A3C20Incheon
  1. 두 CSV를 각각 Power Query로 가져옵니다.Excel의 Data → From Text/CSV에서 파일별로 Transform Data를 열고 customer_id를 두 쿼리에서 같은 Text 자료형으로 확인합니다.
  2. Sales를 왼쪽, Addresses를 오른쪽에 놓고 병합합니다.Home → Merge Queries에서 양쪽 customer_id를 선택하고 Join Kind는 Left Outer를 사용합니다.
  3. 먼저 매치 수를 기록한 다음 Addresses를 펼칩니다.펼칠 때 address_id만 선택해 판매 ID별 행 수를 비교합니다. 일치하지 않는 C30 판매는 null 주소를 가진 채 남습니다.

02 / AUDIT

중첩 테이블 상태에서
매치 수를 확인합니다

병합 직후에는 오른쪽 결과가 중첩 테이블 열에 들어 있습니다. 주소 열 이름이 Addresses라면 Custom Column에 Table.RowCount([Addresses])를 추가해 각 왼쪽 행의 매치 수를 셉니다. 0은 미일치, 1은 단일 일치, 2 이상은 다중 일치입니다.

원래 판매 ID와 매치 수를 함께 남겨 두면 어떤 행이 늘어날지 펼치기 전에 알 수 있습니다. 진단용으로 Keep Duplicates를 사용한다면 원본 조회의 별도 Reference나 복사본에서만 사용합니다. Keep Duplicates 결과에는 유일한 키가 남지 않습니다. 이 결과를 실제 업무의 병합에 쓰는 lookup으로 돌려 쓰면 정상 고객이 사라집니다.

03 / DECISION

원하는 출력의
행 의미를 결정합니다

모든 주소를 보고 싶은 보고서라면 one-to-many 관계를 유지하고 판매 금액을 주소별 새 매출처럼 합산하지 않습니다. 주소 존재 여부만 필요하다면 중첩 테이블을 펼치지 않고 매치 수가 0보다 큰지 계산할 수 있습니다.

판매당 주소 하나가 필요하다면 선택 규칙이 있어야 합니다. 판매 레코드가 address_id를 갖고 있다면 고객 ID 대신 그 키로 연결하는 것이 가장 명확합니다. “현재 주소”를 선택한다면 고객마다 current=true가 정확히 하나인지, 동률과 누락을 어떻게 처리할지 먼저 정의합니다.

행 수를 원래대로 만드는 것은 목표가 아니라 결과입니다.

잘못된 주소 하나를 남겨도 행 수는 맞을 수 있습니다. ID, 미일치 고객, 선택된 주소와 금액을 같이 확인해야 합니다.

판매 시점에 선택한 주소 ID가 기록돼 있다면 그 값을 사용할 수 있습니다. 다음 파일은 설명을 위해 주소 ID를 함께 넣은 별도 합성 데이터입니다. 실제 데이터에 주소 ID가 없다면 임의로 이 값을 채우지 말고, 담당자와 주소 선택 규칙부터 정하세요.

  1. 새 판매 파일과 기존 주소 파일을 가져옵니다.두 쿼리의 address_id 열을 Text로 맞춥니다. 주소 쿼리에서 address_id가 중복되거나 비어 있는지 먼저 확인합니다.
  2. customer_id와 address_id 두 열로 Left Outer 병합합니다.양쪽에서 Ctrl 키를 누른 채 customer_id, address_id 순서로 선택합니다. 고객과 주소가 모두 같은 행만 연결해 다른 고객의 주소 연결도 피합니다.
  3. city를 펼치고 원본 판매 ID와 금액을 대조합니다.S1부터 S4까지 한 행씩, 합계 42가 남아야 합니다. 주소가 없는 S4는 null이며 판매 자체는 유지합니다.
주소 선택이 명시된 병합 결과 · 판매 4행, 합계 42
sale_idaddress_idcityamount
S1A1Seoul10
S2A2Busan20
S3A3Incheon5
S4빈 값null7

04 / CAUTION

정렬 후 Remove Duplicates가
최신 행을 보장한다고 가정하지 않습니다

Power Query의 Table.Distinct는 중복 레코드 중 어느 행이 보존될지 일반적으로 보장하지 않습니다. 단순히 날짜 내림차순 정렬 후 고객 ID 중복을 제거해 최신 주소가 남을 것이라고 믿으면 새로고침이나 쿼리 최적화에서 다른 결과가 생길 수 있습니다.

최신 레코드가 필요하다면 고객별 최대 날짜를 구한 뒤 원본과 다시 매치하고, 같은 최대 날짜가 여러 건인 상황에 tie-break 규칙을 둡니다. 주소 두 개가 모두 실제로 유효하다면 하나를 삭제하지 말고 보고서의 목적에 맞춰 관계를 보존합니다.

05 / VERIFY

판매 4건·합계 42를
여러 각도에서 대조합니다

원본 판매 ID 네 개가 모두 남았는지, 매치 수가 2·2·1·0인지, 펼친 결과가 여섯 행인지 확인합니다. 한 주소 선택 규칙을 적용했다면 선택된 주소 값과 미일치 고객의 null 처리도 봅니다. 마지막으로 보고서가 판매 단위라면 합계가 42인지 대조합니다.

customer_id 기준 Left Outer 병합 후 주소를 펼친 결과
sale_idamountaddress_id
S110A1
S110A2
S220A1
S220A2
S35A3
S47null

왼쪽 판매 행 단위의 금액 합계는 42입니다. 펼친 표의 amount를 단순 합산하면 C10의 판매 10과 20이 두 번씩 포함되어 72가 됩니다. 주소를 하나로 줄여야 한다면 먼저 어떤 주소를 남길지 정하고, 그 규칙을 적용한 결과를 원본 판매 ID와 대조하세요.

아래 실습에서는 같은 합성 데이터를 쿼리 안에 넣어 실제 Excel 엔진에서 확인했습니다. 외부 파일이나 DB를 연결할 때의 folding, privacy level, connector 동작까지 검증한 것은 아닙니다.

Microsoft 병합 함수 설명 입력·예상 CSV가 있는 영문 가이드

06 / RUN THE EXPERIMENT

파일 없이 실행하고,
병합 키 하나의 차이를 확인하세요

실무 파일의 경로나 고객 정보를 바꾸기 전에 작은 데이터로 원리를 확인할 수 있습니다. 아래 쿼리에는 판매 네 건과 주소 세 건이 모두 들어 있습니다. 앞의 CSV를 읽지 않는 독립 실습이며, 주소 ID가 포함된 판매 예제를 기준으로 합니다. 로그인이나 외부 서비스 연결은 필요하지 않습니다.

  1. 새 통합 문서에서 빈 쿼리를 만듭니다.Windows Excel의 데이터 → 데이터 가져오기 → 기타 원본에서 → 빈 쿼리로 들어갑니다. Power Query 편집기의 홈 → 고급 편집기에서 기존 내용을 지우고 아래 전체 코드를 넣습니다. 메뉴 이름은 Excel 버전과 언어에 따라 다를 수 있습니다.
  2. 먼저 audit 모드로 미리 보기를 확인합니다.Mode = "audit"를 유지합니다. sale_id 순서대로 match_count가 2, 2, 1, 0이면 고객 ID만으로 연결할 때 어떤 판매가 반복될지 확인한 것입니다.
  3. 첫 번째 Mode 대입문만 바꿔 비교합니다.Mode = "expanded"로 바꾸면 잘못된 금액 합계를, Mode = "corrected"로 바꾸면 고객 ID와 주소 ID를 함께 쓰는 결과를 봅니다. 아래 조건식의 문자열까지 일괄 치환하지 마세요. 닫기 및 로드로 표를 시트에 놓아 amount를 합산해도 됩니다.
전체 M 코드 보기 · 다운로드와 동일한 내용
// Synthetic learning data only. Change Mode to audit, expanded, or corrected.
let
    Mode = "audit",
    Sales = #table(
        type table [sale_id=text, customer_id=text, address_id=nullable text, amount=number],
        {{"S1","C10","A1",10}, {"S2","C10","A2",20},
         {"S3","C20","A3",5}, {"S4","C30",null,7}}
    ),
    Addresses = #table(
        type table [address_id=text, customer_id=text, city=text],
        {{"A1","C10","Seoul"}, {"A2","C10","Busan"}, {"A3","C20","Incheon"}}
    ),
    ByCustomer = Table.NestedJoin(Sales, {"customer_id"}, Addresses, {"customer_id"}, "matches", JoinKind.LeftOuter),
    Audit = Table.AddColumn(ByCustomer, "match_count", each Table.RowCount([matches]), Int64.Type),
    Expanded = Table.ExpandTableColumn(Audit, "matches", {"city"}, {"city"}),
    KeyCounts = Table.Group(Addresses, {"customer_id", "address_id"}, {{"count", each Table.RowCount(_), Int64.Type}}),
    BadKeys = Table.SelectRows(KeyCounts, each [count] <> 1 or [customer_id] = "" or [address_id] = ""),
    CheckedAddresses = if Table.RowCount(BadKeys) > 0 then error "Address lookup keys must be nonempty and unique" else Addresses,
    ByAddress = Table.NestedJoin(Sales, {"customer_id", "address_id"}, CheckedAddresses, {"customer_id", "address_id"}, "matches", JoinKind.LeftOuter),
    Corrected = Table.ExpandTableColumn(ByAddress, "matches", {"city"}, {"city"}),
    Selected = if Mode = "audit" then Table.RemoveColumns(Audit, {"matches"})
        else if Mode = "expanded" then Expanded
        else if Mode = "corrected" then Corrected
        else error "Mode must be audit, expanded, or corrected",
    Result = Table.Sort(Selected, {{"sale_id", Order.Ascending}})
in
    Result
2026. 10. 05. 실제 Excel 엔진 실행 결과 · 합성 데이터

넓은 표는 좌우로 스크롤할 수 있습니다. 키보드는 Tab으로 표 영역에 이동한 뒤 좌우 방향키를 누르세요.

Mode출력 행 수amount 합계확인할 값
audit442match_count: 2, 2, 1, 0
expanded672S1·S2 각각 두 행
corrected442S1 Seoul, S2 Busan, S3 Incheon, S4 null

왜 corrected는 결과를 임의로 한 건씩 지우지 않을까요? 판매가 가리키는 주소 ID와 고객 ID를 함께 비교하므로 S1은 A1, S2는 A2와 연결됩니다. S4는 주소가 없지만 Left Outer 덕분에 판매 금액 7이 남습니다. 중복 삭제로 합계만 맞추는 것과 달리 어떤 주소를 선택했는지도 설명할 수 있습니다.

잘못된 조회 테이블도 넣어 보세요. Addresses의 첫 번째 행 바로 뒤에 {"A1","C10","Duplicate"},를 추가하고 corrected 모드를 실행합니다. 같은 고객·주소 키가 두 건이므로 Address lookup keys must be nonempty and unique 오류로 멈춥니다. audit와 expanded는 문제를 관찰하는 모드여서 이 검사를 적용하지 않습니다. 실무에서는 오류를 숨기기보다 원본 주소의 중복 사유와 담당자를 확인해야 합니다.

실행한 범위와 아직 확인하지 않은 범위

Windows Excel 16.0, build 5569.0의 Microsoft Mashup OLE DB 엔진에서 이 전체 쿼리를 실행했습니다. 세 모드의 행 수·합계, 매치 수, 수정 후 전체 레코드, 중복 키 오류까지 9개 검사를 통과했습니다. 메뉴를 마우스로 따라 누른 UI 테스트나 Mac·웹 Excel 호환성, 외부 DB 연결 검증은 아닙니다. 예제의 키는 짧은 합성 문자열입니다. 실데이터에서는 공백·null·자료형·대소문자 규칙도 따로 정해야 합니다.

미리 보기가 예상과 다르면 첫 줄의 Mode, 입력 행, 마지막 in Result를 먼저 비교하세요. 주소 순서는 보장하지 않으므로 expanded의 같은 판매 안에서 Seoul과 Busan의 표시 순서가 다른 것은 오류가 아닙니다. 반대로 corrected에서 S4가 없어지거나 합계가 달라졌다면 조인 종류와 원본 입력부터 다시 확인해야 합니다.

함수 동작의 기준은 Microsoft의 Table.NestedJoin과 Table.ExpandTableColumn 문서입니다. 이 실험은 해당 동작을 작은 판매 데이터로 재현한 것으로, 모든 보고서에서 행 수를 줄여야 한다는 규칙은 아닙니다.