본문으로 건너뛰기
clickhouse검색
SQL과 쿼리

집계 함수와 컴비네이터 (-If, -Array, -State, -Merge, uniq, quantile)

ClickHouse 집계 함수의 핵심인 컴비네이터와 근사 함수를 정리합니다. 한 번 읽으면 CASE WHEN 더미와 서브쿼리 없이 집계 쿼리를 절반 길이로 줄일 수 있고, uniq와 quantile의 오차를 알고 고를 수 있습니다.

집계컴비네이터uniqquantileargMaxAggregateFunction2026-10 기준, 약 12분

왜 컴비네이터부터 배우나

ClickHouse 집계 함수의 특징은 함수 이름 뒤에 접미사(컴비네이터) 를 붙여 동작을 바꾸는 점입니다. countIf, sumArray, uniqState처럼요. 다른 DB에서 SUM(CASE WHEN ... THEN 1 ELSE 0 END)로 쓰던 것이 countIf(...) 하나로 끝나고, 배열 컬럼을 풀지 않고 집계할 수 있고, 중간 상태를 저장해 뒤에 합칠 수 있습니다.

SELECT
    toDate(event_time) AS d,
    count() AS pv,
    countIf(status >= 500) AS err,
    uniq(user_id) AS uv,
    uniqIf(user_id, is_new = 1) AS new_uv,
    quantile(0.95)(duration_ms) AS p95,
    argMax(url, duration_ms) AS slowest_url
FROM web_logs
WHERE event_time >= today() - 7
GROUP BY d
ORDER BY d;

자주 쓰는 기본 집계 함수

함수하는 일메모
count(), count(col)행 수, NULL 아닌 값 수count(*)도 됩니다
sum, avg, min, max표준과 같음sum은 넘침을 막기 위해 더 큰 타입으로 계산
any, anyLast그룹의 임의 값 하나그룹에서 "대표값 하나만" 필요할 때 GROUP BY에 넣지 않고 꺼냅니다
argMax(a, b), argMin(a, b)b가 최대(최소)인 행의 a최신 상태 뽑기의 표준 패턴
groupArray, groupUniqArray값을 배열로 모음순서 보장은 없음. 정렬이 필요하면 groupArraySorted(n) 또는 arraySort
topK(n), topKWeighted빈도 상위 n개 근사정확한 순위는 GROUP BY + ORDER BY
sumMap, maxMapMap 또는 키 배열과 값 배열을 키별로 합산카운터 맵 집계에 유용
avgWeighted(x, w)가중 평균
groupBitAnd, groupBitmap비트 연산, 비트맵사용자 세그먼트 교집합 등
varPop, stddevPop, corr통계표본 버전은 varSamp, stddevSamp
sparkbar(n), histogram(n)작은 시각화, 히스토그램탐색용

팁 "각 사용자의 마지막 로그인 시각과 그때의 IP"는 argMax(ip, login_at)과 max(login_at)을 같은 GROUP BY에서 꺼내면 끝입니다. 윈도우 함수나 자기 조인이 필요하지 않습니다.

컴비네이터 한눈에

접미사뜻예
-If조건을 만족하는 행만sumIf(amount, status = 'paid')
-Array배열 컬럼의 원소를 펼쳐서 집계sumArray(items_price), uniqArray(tags)
-MapMap의 값을 키별로 집계sumMap(counters)
-State중간 상태를 돌려줌 (AggregateFunction 타입)uniqState(user_id)
-Merge중간 상태들을 합쳐 최종값uniqMerge(uv_state)
-MergeState상태들을 합쳐 다시 상태로롤업 테이블을 더 큰 단위로 다시 집계
-SimpleStateSimpleAggregateFunction용 상태maxSimpleState(x)
-ForEach배열의 같은 위치끼리 집계sumForEach([1,2],[3,4])는 [4,6]
-Distinct중복 값을 하나로sumDistinct(x)
-OrDefault, -OrNull빈 그룹이면 기본값 또는 NULLmaxOrNull(x), avgOrDefault(x)
-Resample구간별로 나눠 배열로 집계countResample(0, 100, 10)(age)
-ArgMin, -ArgMax다른 컬럼이 최소, 최대인 행만 대상으로sumArgMax(amount, version)

컴비네이터는 겹쳐 쓸 수 있습니다. uniqArrayIf(tags, status = 200)은 조건에 맞는 행의 배열을 펼쳐 고유 수를 셉니다. countIfState, sumMapMerge처럼 상태 컴비네이터와도 섞입니다. 접미사 순서는 안쪽 동작이 먼저 오도록, 보통 -Array, -If, 그다음 -State 순으로 붙입니다.

-- 조건별 집계를 CASE 없이
SELECT
    device,
    countIf(status < 400) AS ok,
    countIf(status BETWEEN 400 AND 499) AS client_err,
    countIf(status >= 500) AS server_err,
    avgIf(duration_ms, status < 400) AS ok_avg_ms
FROM web_logs
GROUP BY device;

-- 배열 컬럼을 ARRAY JOIN 없이
SELECT uniqArray(tags) AS distinct_tags, sumArray(item_prices) AS gmv
FROM orders
WHERE order_date = today();

근사 함수의 오차를 알고 고르기

uniq 가족

함수방식오차메모리쓸 때
uniq적응형 샘플링 해시보통 1% 안쪽작음대시보드 기본값
uniqCombined, uniqCombined64HyperLogLog++ 계열약 0.5% 안쪽 (정밀도 인자로 조정)작고 안정적상태를 저장하는 롤업 테이블
uniqHLL12HyperLogLog 고정수 %2.5KB 고정메모리가 아주 빡빡할 때
uniqThetaTheta sketch교집합, 차집합 연산 가능중간세그먼트 겹침 계산
uniqExact해시 집합0고유 값 수에 비례정산, 보고서

count(DISTINCT x)는 설정 count_distinct_implementation이 가리키는 함수(기본값 uniqExact)로 바뀝니다. 대시보드에서 count(DISTINCT user_id)를 쓰면 메모리를 많이 쓰니 uniq로 바꿀 것인지 먼저 정합니다.

quantile 가족

함수방식특징
quantile, median저수지 샘플링 (기본 8192개)빠르고 메모리가 일정. 데이터가 많으면 결과가 조금씩 흔들림
quantileExact전부 메모리에 올려 정렬정확, 메모리 큼
quantileTDigestt-digest양 끝 분위수(p99.9)에 강함, 상태 작음
quantileTiming밀리초 지연 특화정확도와 속도 균형, 30초 넘는 값은 뭉개짐
quantileBFloat1616비트 근사아주 빠름, 오차 큼
quantileDDDDSketch상대 오차 보장

여러 분위수를 한 번에 구할 때는 quantiles(0.5, 0.9, 0.99)(x)처럼 복수형을 쓰면 데이터를 한 번만 읽습니다. 함수마다 quantilesTDigest, quantilesTiming이 있습니다.

SELECT
    service,
    quantilesTiming(0.5, 0.95, 0.99)(duration_ms) AS p,
    p[1] AS p50, p[2] AS p95, p[3] AS p99
FROM requests
WHERE event_time >= now() - INTERVAL 1 HOUR
GROUP BY service
ORDER BY p99 DESC;

참고 분위수 함수에 넘기는 값이 정렬 키의 일부라면 optimize_aggregation_in_order가 켜졌을 때 메모리를 덜 씁니다. 그래도 근사 함수 선택이 메모리에 가장 큰 영향을 줍니다.

-State와 -Merge: 미리 집계해 두기

-State는 집계의 중간 결과를 AggregateFunction(함수, 타입) 값으로 돌려줍니다. 이 값은 사람이 읽을 수 없지만 합칠 수 있습니다. 시간별 롤업 테이블에 상태를 저장해 두면, 일별이나 월별 고유 사용자 수를 다시 원본을 읽지 않고 -Merge로 구합니다. 머티리얼라이즈드 뷰와 AggregatingMergeTree의 핵심입니다.

CREATE TABLE daily_uv
(
    d       Date,
    country LowCardinality(String),
    uv      AggregateFunction(uniqCombined, UInt64),
    pv      SimpleAggregateFunction(sum, UInt64)
)
ENGINE = AggregatingMergeTree
ORDER BY (d, country);

CREATE MATERIALIZED VIEW daily_uv_mv TO daily_uv AS
SELECT toDate(event_time) AS d, country,
       uniqCombinedState(user_id) AS uv, count() AS pv
FROM web_logs
GROUP BY d, country;

-- 조회: 상태를 합쳐 최종값으로
SELECT d, uniqCombinedMerge(uv) AS uv, sum(pv) AS pv
FROM daily_uv
WHERE d >= today() - 30
GROUP BY d ORDER BY d;
  • uniqCombinedMerge처럼 같은 함수 이름으로 합쳐야 합니다. uniqState를 uniqCombinedMerge로 합칠 수 없습니다.
  • sum, max, min, any, groupBitOr처럼 상태가 값 자체인 함수는 SimpleAggregateFunction을 쓰면 저장과 조회가 단순해집니다.
  • -MergeState는 상태를 합쳐 다시 상태로 돌려주므로 일별 롤업에서 월별 롤업을 만들 때 씁니다.
  • 상태 컬럼은 finalizeAggregation(state)로 바로 최종값을 볼 수 있습니다.

전체 설계는 머티리얼라이즈드 뷰와 MergeTree 가족에서 이어집니다.

GROUP BY 메모리와 설정

큰 GROUP BY에서 메모리 초과가 나면 다음 순서로 손봅니다.

  1. 근사 함수로 바꾸기 (uniqExact를 uniq로, quantileExact를 quantileTDigest로).
  2. 키 컬럼을 LowCardinality(String)으로, 고카디널리티 문자열 키는 해시나 숫자 ID로.
  3. max_bytes_before_external_group_by를 두어 디스크로 흘리기. 보통 max_memory_usage의 절반 정도로 잡습니다.
  4. 정렬 키 접두어로 집계한다면 optimize_aggregation_in_order = 1.
  5. max_rows_to_group_by와 group_by_overflow_mode = 'any'로 상위 그룹만 보고 멈추기 (탐색용).
SELECT url, uniq(user_id) AS uv
FROM web_logs
GROUP BY url
ORDER BY uv DESC
LIMIT 100
SETTINGS max_bytes_before_external_group_by = 10000000000,
         max_memory_usage = 20000000000;

메모리 오류의 원인 분류는 메모리 초과 트러블슈팅에 있습니다.

WITH TOTALS, ROLLUP, CUBE, GROUPING SETS

SELECT country, device, count() AS c
FROM web_logs
WHERE event_date = today()
GROUP BY country, device WITH ROLLUP
ORDER BY country, device;
  • WITH TOTALS는 결과 뒤에 전체 합계 행을 따로 붙입니다. totals_mode로 HAVING에 걸린 행을 합계에 넣을지 정합니다.
  • ROLLUP은 (country, device), (country), () 순서로 상위 합계를 만듭니다.
  • GROUPING SETS로 필요한 조합만 고르고, grouping(country)로 어느 수준의 행인지 구분합니다.

집계 쿼리를 쓸 때 체크리스트

  • 조건 집계는 -If, 배열은 -Array, 두 개 이상이면 겹쳐 쓰기.
  • 최신 상태는 argMax, 대표값은 any.
  • 고유 수는 목적에 맞는 uniq*. 정산은 uniqExact.
  • 분위수는 quantiles*(...)로 한 번에. 지연시간은 quantileTiming 또는 quantileTDigest.
  • 반복되는 집계는 -State로 롤업 테이블에 저장하고 -Merge로 읽기.
  • 메모리 초과는 근사 함수, LowCardinality, 외부 집계 순으로.

공식 문서

이 문서에서