DAX CALCULATE 함수 정리: 필터를 바꿔서 계산한다는 게 무슨 뜻일까

측정값 하나 만들어서 표에 올리면 지역별 합계가 알아서 나뉘어 나옵니다. 여기까지는 SUM 하나로 끝납니다. 그런데 “서울 매출만 따로 보고 싶다”, “전체에서 이 지역이 차지하는 비중을 알고 싶다” 같은 요구가 나오면 SUM만으로는 안 됩니다. 이때 처음 만나는 함수가 CALCULATE입니다.

CALCULATE는 DAX에서 가장 중요한 함수로 자주 꼽히고, 동시에 입문자가 가장 오래 붙잡고 있는 함수이기도 합니다. 문법 자체는 짧은데 동작 규칙이 몇 가지 숨어 있어서 그렇습니다. 이 글에서는 문법부터 필터 덮어쓰기, KEEPFILTERS, 컨텍스트 전환까지 한 번에 정리해 보겠습니다.

한 줄로 말하면

CALCULATE는 필터 조건을 바꾼 상태에서 식을 다시 계산하는 함수입니다. Microsoft 문서의 설명도 “수정된 필터 컨텍스트에서 식을 평가한다”는 취지입니다. 보고서의 각 셀에는 이미 슬라이서, 행 머리글, 페이지 필터가 만든 “현재 필터 상태”가 걸려 있는데, CALCULATE는 그 상태를 잠깐 고쳐서 계산한 결과를 돌려줍니다.

문법과 인수

CALCULATE( <식> [, <필터1> [, <필터2> [, ...]]] )
  • 첫 번째 인수(식): 계산할 대상입니다. 측정값처럼 동작하는 식이면 됩니다. 보통 SUM이나 이미 만들어 둔 측정값을 넣습니다.
  • 두 번째 인수부터(필터): 생략해도 되고, 여러 개 넣어도 됩니다. 반환값은 필터를 바꾼 상태에서 계산한 식의 결과입니다.

필터 자리에는 세 종류가 들어갑니다.

종류설명모양
참/거짓 조건열 값에 조건을 거는 가장 흔한 형태Sales[지역] = “서울”
테이블 필터테이블을 돌려주는 식. FILTER 함수 등을 씀FILTER( … )
필터 수정 함수기존 필터를 지우거나, 유지하거나, 관계를 바꿈REMOVEFILTERS, KEEPFILTERS, USERELATIONSHIP 등

예제 데이터

아래 예제는 전부 이 작은 표를 기준으로 합니다. 설명하려고 제가 임의로 만든 가상 데이터이고, 금액 단위는 만원입니다. 그리고 뒤에 나오는 결과 표는 Power BI에서 직접 돌린 화면이 아니라 계산 원리를 따라가며 손으로 구한 값입니다.

주문번호지역상품금액
1서울노트북120
2서울마우스10
3서울모니터40
4부산노트북100
5부산마우스20
6대구모니터30

총합은 320, 지역별로는 서울 170, 부산 120, 대구 30입니다. 기본 측정값은 이렇게 잡아 두겠습니다.

총매출 = SUM( Sales[금액] )

예제 1. 조건 하나 걸기

서울매출 =
CALCULATE(
    [총매출],
    Sales[지역] = "서울"
)

“지역이 서울인 상태에서 총매출을 계산해라”라는 뜻입니다. 이 측정값을 지역별 표에 올리면 결과가 이렇게 나옵니다.

지역총매출서울매출
서울170170
부산120170
대구30170
합계320170

부산 줄에 170이 나오는 게 이상해 보입니다. 오류가 아니라 CALCULATE의 기본 동작입니다. 바로 다음 항목에서 이유를 설명합니다.

같은 열의 필터는 덮어쓴다

부산 줄에는 원래 “지역 = 부산”이라는 필터가 걸려 있습니다. 그런데 CALCULATE 안에 같은 열(지역)에 대한 필터가 들어오면, 기존 필터는 합쳐지지 않고 교체됩니다. 그래서 부산 줄에서도 서울 기준으로 계산됩니다.

이유는 내부 변환에 있습니다. DAX Guide와 SQLBI 모두 설명하는 내용인데, Sales[지역] = "서울" 같은 조건은 엔진이 내부적으로 아래처럼 바꿔서 처리합니다.

FILTER( ALL( Sales[지역] ), Sales[지역] = "서울" )

ALL이 지역 열의 기존 필터를 먼저 걷어내기 때문에, 바깥에서 걸린 필터가 사라지는 것입니다. 한 가지 더, 필터 인수는 CALCULATE가 컨텍스트를 바꾸기 전에, 즉 바깥의 원래 컨텍스트에서 먼저 계산됩니다. 이 순서 때문에 이미 필터가 걸린 열을 다룰 때 결과가 예상과 달라질 수 있습니다.

예제 2. 조건을 여러 개 걸 때

서울 노트북 =
CALCULATE(
    [총매출],
    Sales[지역] = "서울",
    Sales[상품] = "노트북"
)

필터 인수를 여러 개 나열하면 모두 만족해야 하는 AND 조건으로 묶입니다. 서울이면서 노트북인 행은 1번 주문 하나라 120입니다. 위 덮어쓰기 규칙 때문에 지역별 표에 올리면 모든 줄에 120이 찍힙니다.

OR 조건이 필요하면 한 조건식 안에서 ||를 씁니다.

서울 또는 대구 =
CALCULATE(
    [총매출],
    Sales[지역] = "서울" || Sales[지역] = "대구"
)

결과는 170 + 30 = 200입니다. 다만 하나의 조건식 안에 쓸 수 있는 열은 같은 테이블의 열이어야 합니다. 예를 들어 Sales의 수량 조건과 Product의 색상 조건을 ||로 묶는 식은 허용되지 않습니다. 이런 경우는 다른 방식(테이블 필터 등)으로 풀어야 합니다.

AND 조건은 &&로 한 식에 묶을 수도 있지만, SQLBI는 성능 면에서 인수를 따로 나눠 쓰기를 권합니다. 같은 효과이니 나눠 쓰는 쪽이 낫습니다.

예제 3. 전체 대비 비중

지역비중 =
DIVIDE(
    [총매출],
    CALCULATE( [총매출], REMOVEFILTERS( Sales[지역] ) )
)

분모 쪽 CALCULATE가 지역 필터를 모두 걷어내서 전체 합계 320을 만듭니다. 분자는 현재 줄의 값이고요. DIVIDE는 분모가 0이거나 비어 있을 때 오류 대신 빈 값을 돌려주는 나눗셈 함수라서 비율 계산에 많이 씁니다.

지역총매출지역비중
서울17053.1%
부산12037.5%
대구309.4%
합계320100.0%

필터를 지우는 함수로 ALL도 있습니다. 다만 ALL은 필터를 지우는 역할과 테이블을 돌려주는 역할을 겸하는 함수라서, 필터를 지우는 목적이라면 REMOVEFILTERS가 의도를 더 분명하게 보여줍니다. Microsoft 문서도 REMOVEFILTERS를 쓸 수 있는 환경이라면 그쪽을 권하는 취지로 적어 두었습니다.

덮어쓰지 않고 겹치고 싶을 때: KEEPFILTERS

서울매출_교차 =
CALCULATE(
    [총매출],
    KEEPFILTERS( Sales[지역] = "서울" )
)

KEEPFILTERS로 감싸면 기존 필터를 지우지 않고 새 필터와 교집합을 만듭니다. 서울 줄은 “서울이면서 서울”이라 170, 부산 줄은 “부산이면서 서울”이라 해당 행이 없어 빈 값이 됩니다.

지역서울매출(덮어쓰기)서울매출_교차(KEEPFILTERS)
서울170170
부산170(빈 값)
대구170(빈 값)
합계170170

CALCULATE가 실제로 일하는 순서

DAX Guide에는 평가 순서가 정리되어 있습니다. 그대로 옮기지 않고 흐름만 풀어 쓰면 이렇습니다.

  1. 필터 인수들을 바깥의 원래 컨텍스트에서 각각 계산해 둡니다.
  2. 현재 필터 컨텍스트를 복사하고, 행 컨텍스트는 버립니다.
  3. 행 컨텍스트가 있었다면 그 값을 필터로 바꿔 넣습니다(컨텍스트 전환).
  4. USERELATIONSHIP, CROSSFILTER, ALL 계열 같은 수정 함수를 적용합니다.
  5. 마지막으로 1번에서 계산한 필터 인수를 적용합니다.

실무에서 이 순서를 전부 외울 필요는 없습니다. “필터 인수는 바깥 기준으로 먼저 계산되고, 같은 열이면 덮어쓴다” 정도만 기억해도 대부분의 의문이 풀립니다.

컨텍스트 전환

CALCULATE에는 필터를 하나도 넣지 않아도 하는 일이 있습니다. 행 컨텍스트를 필터 컨텍스트로 바꾸는 것입니다.

예를 들어 상품 테이블에 계산 열을 만든다고 해 보겠습니다. 상품 테이블과 Sales 사이에 관계가 있다고 가정합니다.

상품매출 = SUM( Sales[금액] )                 -- 모든 줄에 320
상품매출 = CALCULATE( SUM( Sales[금액] ) )    -- 노트북 220, 마우스 30, 모니터 70

첫 번째 식은 계산 열의 행 컨텍스트가 Sales를 걸러주지 못해서 모든 줄에 전체 합계 320이 들어갑니다. 두 번째 식은 CALCULATE가 “현재 행의 상품”을 필터로 바꿔서 그 상품의 합계만 계산합니다.

이 동작은 SUMX 같은 반복 함수 안에서도 일어납니다. 한 가지 편한 점은, 이미 만들어 둔 측정값을 행 컨텍스트 안에서 참조하면 CALCULATE를 직접 쓰지 않아도 자동으로 컨텍스트 전환이 일어난다는 것입니다. 측정값 참조가 내부적으로 CALCULATE를 감싸는 것과 같기 때문입니다. 반대로 말하면, 반복 함수 안에서 측정값을 쓸 때 값이 예상 밖으로 나오면 이 전환이 원인일 수 있습니다.

쓸 때 걸리는 제약

  • 참/거짓 조건에는 한 테이블의 열만 쓸 수 있습니다.
  • 참/거짓 조건 안에서 측정값을 참조할 수 없습니다. 측정값 기준으로 거르려면 FILTER로 테이블 필터를 만들어야 합니다.
  • 참/거짓 조건 안에 CALCULATE를 겹쳐 쓸 수 없습니다.
  • DirectQuery 모드에서는 계산 열과 행 수준 보안 규칙 안에서 CALCULATE를 쓸 수 없습니다.

FILTER로 테이블 전체를 거는 방식도 가능은 합니다. 다만 SQLBI는 열 단위로 거는 것이 더 가볍고 의미상 안전하다고 설명합니다. FILTER를 테이블 통째로 걸면 그 테이블의 모든 열이 영향을 받아서 예상 못한 결과가 나올 수 있기 때문입니다. 꼭 필요하지 않다면 열 단위 조건을 먼저 고려하세요.

자주 하는 실수

  1. 모든 줄에 같은 값이 나온다: 같은 열의 필터를 덮어쓴 경우입니다. 의도한 결과인지 보고, 겹쳐야 하면 KEEPFILTERS를 씁니다.
  2. 조건에 측정값을 넣었더니 오류가 난다: 참/거짓 조건에서는 측정값을 쓸 수 없습니다.
  3. 계산 열 값이 전부 같다: 행 컨텍스트를 필터로 바꾸는 CALCULATE를 빼먹었을 수 있습니다.
  4. 수식 오류가 난다: 괄호와 쉼표를 확인하세요. 지역 설정에 따라 인수 구분자가 쉼표 대신 세미콜론일 수 있습니다.

비슷한 함수와의 차이

  • CALCULATETABLE: 필터를 바꾸는 방식은 같고, 결과로 값이 아니라 테이블을 돌려줍니다.
  • SUMX: 테이블을 한 줄씩 돌며 식을 계산해 더합니다. 필터 컨텍스트를 바꾸는 함수가 아닙니다.
  • FILTER: 조건에 맞는 행만 남긴 테이블을 만듭니다. CALCULATE의 필터 인수로 자주 들어갑니다.
  • REMOVEFILTERS / ALL: CALCULATE 안에서 필터를 걷어낼 때 씁니다.

정리

CALCULATE는 결국 “지금 걸린 필터를 내가 원하는 모양으로 바꿔서 다시 계산한다”는 한 가지 일을 합니다. 같은 열이면 덮어쓰고, 겹치고 싶으면 KEEPFILTERS, 지우고 싶으면 REMOVEFILTERS, 행 컨텍스트가 있으면 필터로 바꿔 줍니다. 이 네 가지만 구분해도 이후에 나오는 시간 비교 함수나 비율 계산이 훨씬 읽기 쉬워집니다.

이 글의 예제는 가상 데이터이니, 직접 따라 하실 때는 본인 모델의 열 이름으로 바꿔서 확인해 보세요. 함수 동작은 Power BI 버전에 따라 달라질 수 있으니 공식 문서도 함께 보시길 권합니다.

참고한 자료

  • Microsoft Learn, CALCULATE function (DAX): https://learn.microsoft.com/en-us/dax/calculate-function-dax
  • DAX Guide, CALCULATE: https://dax.guide/calculate/
  • SQLBI, Introducing CALCULATE in DAX: https://www.sqlbi.com/articles/introducing-calculate-in-dax/
  • SQLBI, Specifying multiple filter conditions in CALCULATE: https://www.sqlbi.com/articles/specifying-multiple-filter-conditions-in-calculate/
  • SQLBI, Context transition in DAX explained visually: https://www.sqlbi.com/articles/context-transition-in-dax-explained-visually/

“DAX CALCULATE 함수 정리: 필터를 바꿔서 계산한다는 게 무슨 뜻일까”에 대한 2개의 생각

댓글 남기기