Distinct count의 합계는 세부행의 합이 아니라, 합계 범위에서 고유 ID를 다시 센 값이다. 따라서 채널별 사용자가 Web 80명, Mobile 50명이어도 두 채널을 모두 쓴 30명이 있다면 합계는 130명이 아니라 100명이다.
대시보드 표의 마지막 행이 위 숫자를 더한 값과 다르면 먼저 오류를 의심하게 된다. 합계가 더 작으면 누락처럼 보인다. 하지만 사용자·주문·계정처럼 같은 ID가 여러 그룹에 나타날 수 있는 지표는 행을 더할 수 없는 Non-additive Metric이다. 화면은 계산 결과만 보여줄 것이 아니라, 어떤 단위에서 무엇을 다시 세었는지 설명해야 한다.
합계 100명은 80명과 50명을 다시 계산한 결과다
아래 값은 설명을 위한 합성 이용 기록이다. 실제 고객·제품·성과를 나타내지 않는다. 1주 동안 Web을 쓴 고유 사용자는 80명, Mobile을 쓴 고유 사용자는 50명이다. 이 가운데 30명은 두 채널을 모두 썼다.
| 범위 | 계산과 해석 |
|---|---|
| Web | 80명 — Web 범위의 고유 user_id |
| Mobile | 50명 — Mobile 범위의 고유 user_id |
| 두 채널 중복 | 30명 — 세부행을 더하면 두 번 포함되는 user_id |
| 전체 합계 | 100명 = 80 + 50 − 30 — 주간 전체 범위에서 채널 차원을 제거하고 고유 user_id를 다시 셈 |
Microsoft의 DAX 공식 문서도 DISTINCTCOUNT 합계는 additive하지 않으며, 같은 주문이 여러 category에 포함되면 category 값에는 각각 나타나지만 Grand Total에서는 한 번만 센다고 설명한다. Google Cloud의 Looker 문서 역시 그룹별 고유 사용자 수를 더한 값보다 전체 distinct user total이 작아지는 이유를 그룹 간 중복으로 설명한다. 도구가 다르더라도 원리는 같다.
숫자보다 먼저 Aggregate Contract를 정한다
합계행 설계는 “SUM을 쓸 것인가”로 시작하지 않는다. 지표의 집계 계약을 다섯 항목으로 쓴다. 독자가 이 Contract만 보더라도 합계의 단위와 예외를 이해할 수 있어야 한다.
- Entity: user_id → 화면 라벨 “고유 사용자”
- 행 Grain: 주 × 채널 → 기간 “10월 1–7일”, 행 “채널”
- 합계 Scope: 주(채널 차원 제거) → 전체 주간 범위에서 고유 user_id를 다시 셈
- Aggregation: 현재 범위에서 DISTINCTCOUNT(user_id) → 합계 라벨 “전체 고유 사용자”
- Overlap: 한 user_id가 여러 채널에 존재 가능 → 주석 “채널 합과 다를 수 있음”
Aggregate Contract: Entity·행 Grain·합계 Scope·Aggregation·Overlap을 고정하면 합계행이 세부행 합과 다른 이유를 재현할 수 있다.
여기서 첫 번째 시각 우선순위는 전체 고유 사용자 100명이다. 두 번째는 Web 80명과 Mobile 50명의 채널별 규모다. 세 번째는 중복 30명이다. 130명을 ‘총 사용자’로 크게 보여주면 우선순위가 뒤집힌다. 그 값은 전체 사용자 수가 아니라 채널별 고유 사용자 수를 중복 포함해 더한 값이다.
합계를 맞추려고 세부행을 억지로 배분하지 않는다
표의 숫자가 더해지게 만들기 위해 중복 사용자를 한 채널에만 배정할 수도 있다. 예를 들어 ‘첫 방문 채널’ 규칙으로 30명을 Web이나 Mobile 중 한 곳에 귀속시키면 행 합은 100명이 된다. 그러나 질문이 바뀐다. 이제 표는 “각 채널을 사용한 사람”이 아니라 “최초 귀속 채널별 사람”을 보여준다.
이 글이 선택한 안은 채널별 distinct count를 유지하고 합계를 다시 계산하는 방식이다. 한 사람이 여러 채널을 썼다는 사실을 보존하기 때문이다. 대신 합계 라벨과 주석으로 비가산성을 드러낸다. 행 합이 반드시 필요한 용량 계획이라면 ‘고유 사용자’ 대신 세션·주문·접점처럼 더할 수 있는 지표를 별도로 사용한다.
합계행의 네 가지 실패를 구분한다
| 실패 | 진단과 수정 |
|---|---|
| 중복 제거를 누락으로 오해 | 그룹 간 동일 ID를 확인하고 “전체 고유” 라벨과 중복 주석을 둔다. |
| 행 합을 전체로 오인 | 단순 SUM 여부를 확인하고 전체 범위에서 distinct count를 다시 계산한다. |
| 분모가 다른 비율을 합산 | 각 행의 분자·분모를 확인하고 전체 분자 ÷ 전체 분모로 다시 계산한다. |
| 시간 범위를 더함 | 여러 날 방문한 동일 ID를 확인하고 주간 범위에서 다시 distinct count한다. |
표·KPI·Tooltip에는 서로 다른 설명이 필요하다
- 표 합계: “전체 고유 사용자 100명”으로 쓰고 “채널 간 중복을 제거해 재계산”을 바로 아래에 둔다.
- KPI: 100명을 대표값으로 쓰되, 채널별 고유 사용자 수의 합 130명(중복 포함)과 같은 자리에 섞지 않는다.
- Tooltip: Entity=user_id, 기간=10월 1–7일, 중복 허용 단위=채널을 짧게 밝힌다.
- Drill-down: 합계에서 100명의 ID 집합을 보여주고, 채널 행에서는 해당 채널의 ID 집합을 보여준다. 두 목록의 행 수가 더해질 필요는 없다.
모바일에서 네 열 표가 좁아지면 계산식과 해석을 카드로 반복하지 않는다. 핵심 관계인 “80 + 50 − 30 = 100”을 먼저 남기고, Aggregate Contract는 세로 목록으로 바꾼다. 시각 형식은 달라도 Entity·행 Grain·합계 Scope·Aggregation·Overlap 다섯 항목은 줄이지 않는다.
언제 행 합을 사용해도 되는가
- 각 행이 서로 배타적이고 하나의 ID가 한 행에만 속한다.
- 지표가 매출액·수량처럼 현재 분류축에서 실제로 additive하다.
- ‘사용자 수’가 아니라 ‘채널별 사용자 접점’처럼 중복 포함 자체가 질문이다.
- 근사 distinct count를 쓴다면 오차 범위와 계산 방식을 따로 표시한다.
배타성을 확인하지 않고 “카테고리는 겹치지 않을 것”이라고 가정하면 합계 오류가 다시 생긴다. 신규 채널·다중 소속·기간 확장처럼 분류 경계가 바뀔 때 Aggregate Contract도 다시 확인해야 한다.
결정 규칙: 합계를 더하기 전에 중복 가능성을 묻는다
한 Entity가 여러 행 또는 여러 기간에 나타날 수 있다면 세부값을 합하지 말고, 합계 범위에서 지표를 다시 계산한다. 그런 다음 합계 라벨에 집계 단위를 쓰고, 중복 때문에 행 합과 다를 수 있다는 사실을 가까운 곳에 남긴다.
시간에 따라 더할 수 있는 값과 특정 시점의 값을 구분하는 문제는 Stock·Flow 지표 설계에서 이어서 볼 수 있다. 비율 카드에서 표본 수가 사라지는 문제는 표본 수가 다른 비율 비교가 다음 질문이다.
출처와 적용 범위
- Microsoft Learn — DISTINCTCOUNT function (DAX), 2026-10-07 확인. DISTINCTCOUNT의 반환값과 Grand Total이 category 값의 합이 아닌 이유를 확인했다.
- Google Cloud Documentation — Why don’t my totals match the values in my table?, 2026-10-07 확인. 그룹별 distinct user를 더할 때 중복 집계가 발생하고 total query가 전체 범위에서 distinct count를 다시 계산하는 예를 확인했다.
함수·도구 동작은 위 공식 문서에 근거한다. Aggregate Contract, 실패 분류, 표시 우선순위와 결정 규칙은 RUDA의 분석이다. 80명·50명·30명·100명은 계산 관계를 보여주기 위한 합성 값이며 실제 관측치가 아니다.