Search

[필수:정합]M:1:M 조인에 따른 집계 중복

BAD: M00001 회원, 주문 56건·응모 56건(중복 집계)

M:1:M 조인 후 집계
SELECT m.mbr_id ,COUNT(o.ord_no) AS ord_cnt ,COUNT(e.entry_no) AS entry_cnt FROM startdbpg.ms_mbr_big m LEFT OUTER JOIN startdbpg.tr_ord_big o ON (o.mbr_id = m.mbr_id AND o.ord_dtm >= '2024-01-01' AND o.ord_dtm < '2024-02-01') LEFT OUTER JOIN startdbpg.tr_event_entry_big e ON (e.mbr_id = m.mbr_id AND e.entry_dtm >= '2024-01-01' AND e.entry_dtm < '2024-02-01') GROUP BY m.mbr_id ORDER BY m.mbr_id ;
SQL
복사
실행 결과 보기
•
회원 한 명에 주문(여러 건)과 응모(여러 건)를 함께 붙이면 행이 곱해집니다. M00001 회원은 주문 8건 각각에 응모 7건이 모두 붙어 8 × 7 = 56행이 되고, 두 건수가 모두 56으로 나옵니다.
•
M00002처럼 주문·응모가 1건씩이면 결과가 같아 보여서 테스트에서 놓치기 쉽습니다.

GOOD: M00001 회원, 주문 8건·응모 7건(정상)

각 집합을 먼저 집계한 뒤 조인
SELECT m.mbr_id ,COALESCE(o.ord_cnt, 0) AS ord_cnt ,COALESCE(e.entry_cnt, 0) AS entry_cnt FROM startdbpg.ms_mbr_big m LEFT OUTER JOIN ( SELECT o1.mbr_id ,COUNT(*) AS ord_cnt FROM startdbpg.tr_ord_big o1 WHERE o1.ord_dtm >= '2024-01-01' AND o1.ord_dtm < '2024-02-01' GROUP BY o1.mbr_id ) o ON o.mbr_id = m.mbr_id LEFT OUTER JOIN ( SELECT e1.mbr_id ,COUNT(*) AS entry_cnt FROM startdbpg.tr_event_entry_big e1 WHERE e1.entry_dtm >= '2024-01-01' AND e1.entry_dtm < '2024-02-01' GROUP BY e1.mbr_id ) e ON e.mbr_id = m.mbr_id ORDER BY m.mbr_id ;
SQL
복사
실행 결과 보기
데이터 검증 SQL
SELECT 'ord' AS div ,o.mbr_id ,COUNT(*) AS cnt FROM startdbpg.tr_ord_big o WHERE o.ord_dtm >= '2024-01-01' AND o.ord_dtm < '2024-02-01' AND o.mbr_id = 'M00001' GROUP BY o.mbr_id UNION ALL SELECT 'event' AS div ,e.mbr_id ,COUNT(*) AS cnt FROM startdbpg.tr_event_entry_big e WHERE e.entry_dtm >= '2024-01-01' AND e.entry_dtm < '2024-02-01' AND e.mbr_id = 'M00001' GROUP BY e.mbr_id ; div | mbr_id | cnt ------+--------+---- ord | M00001 | 8 event | M00001 | 7
SQL
복사
•
집계 기준이 회원이므로, 주문과 응모를 각각 회원 단위로 먼저 집계해 회원 1명당 1건으로 만듭니다.
•
그러면 회원(1) : 주문 집계(1) : 응모 집계(1)의 1:1:1 조인이 되어 행이 곱해지지 않습니다.
•
인라인 뷰마다 기간 조건을 넣어 1월 데이터만 집계합니다. 주문이나 응모가 없는 회원(M00004)은 NULL이 되므로 COALESCE로 0을 표시합니다.

관계 구조

erDiagram
    ms_mbr_big ||--o{ tr_ord_big : mbr_id
    ms_mbr_big ||--o{ tr_event_entry_big : mbr_id

    ms_mbr_big {
        varchar mbr_id PK
    }

    tr_ord_big {
        numeric ord_no PK
        varchar mbr_id
    }

    tr_event_entry_big {
        varchar event_id PK
        integer entry_no PK
        varchar mbr_id
    }
Mermaid
복사
💡

KEY POINT

•
한 테이블에 여러 상세 테이블을 함께 조인하면 행이 곱해집니다. (주문 8 × 응모 7 = 56)
•
곱해진 상태에서 COUNT·SUM을 하면 건수와 금액이 부풀려집니다.
•
상세 테이블마다 집계 기준에 맞춰 먼저 집계해 1건으로 만든 뒤 조인합니다.
✏️

TIP. DISTINCT로 맞추면 안 되나?

•
COUNT(DISTINCT ord_no)로 건수는 맞출 수 있지만, 늘어난 행은 그대로라 느립니다.
•
SUM(DISTINCT 금액)은 쓰면 안 됩니다. 5,000원 주문이 두 건이면 하나로 합쳐져 5,000원이 됩니다.
•
모든 조합(주문 × 응모)이 실제로 필요한 업무라면 M:1:M 조인 자체는 문제가 아닙니다.
📖 DEEP DIVE