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

ClickHouse SQL 특징 (표준과 다른 점, 별칭, WITH, LIMIT BY, SETTINGS)

PostgreSQL이나 MySQL에 익숙한 사람이 ClickHouse SQL을 처음 쓸 때 걸리는 차이점을 한 번에 정리합니다. 별칭 규칙, WITH 절, LIMIT BY, SETTINGS 절, 타입 변환, NULL 처리까지.

SQL방언LIMIT BYSETTINGS별칭분석기2026-10 기준, 약 11분

한 줄 요약

ClickHouse SQL은 ANSI SQL과 거의 같지만, 분석 쿼리를 짧게 쓰기 위한 확장이 많고 타입이 엄격합니다. 다른 DB에서 넘어온 사람이 처음 걸리는 지점은 대개 다음 다섯 가지입니다.

다른 점한 줄 설명
별칭을 어디서나SELECT에서 지은 별칭을 WHERE, GROUP BY, HAVING, ORDER BY에서 바로 씁니다
함수 이름이 camelCasetoStartOfDay, arrayJoin처럼 대소문자를 구분합니다 (count, sum 같은 일부는 예외)
타입이 엄격정수 나누기 /의 결과는 Float64, 정수 몫은 intDiv. 문자열과 숫자는 자동으로 섞이지 않습니다
NULL이 기본이 아님Nullable(T)로 선언한 컬럼만 NULL을 저장하고, 나머지는 타입의 기본값(0, 빈 문자열)을 가집니다
쿼리 끝에 SETTINGS쿼리마다 SETTINGS max_threads = 8처럼 실행 설정을 붙입니다

별칭은 어디서나 쓸 수 있습니다

표준 SQL에서는 SELECT 절의 별칭을 WHERE에서 쓸 수 없지만 ClickHouse는 됩니다. 긴 표현식을 한 번만 적어도 되니 분석 쿼리가 짧아집니다.

SELECT
    toStartOfHour(event_time) AS h,
    countIf(status >= 500) AS errors,
    count() AS total,
    errors / total AS error_rate
FROM web_logs
WHERE h >= now() - INTERVAL 1 DAY
GROUP BY h
HAVING error_rate > 0.01
ORDER BY h;

주의 별칭 이름이 원래 컬럼 이름과 같으면 어느 쪽을 가리키는지 헷갈립니다. 설정 prefer_column_name_to_alias = 1을 켜면 컬럼 이름을 우선하지만, 처음부터 별칭을 컬럼과 다르게 짓는 편이 안전합니다.

WITH 절은 두 가지 모양

CTE(공통 테이블 식) 말고도 상수와 스칼라 서브쿼리를 이름에 묶는 용도로 자주 씁니다.

-- 1) 상수와 스칼라 서브쿼리
WITH
    toDate('2026-10-01') AS d0,
    (SELECT max(event_date) FROM web_logs) AS d1
SELECT count()
FROM web_logs
WHERE event_date BETWEEN d0 AND d1;

-- 2) 표준 CTE
WITH top_users AS (
    SELECT user_id FROM web_logs GROUP BY user_id ORDER BY count() DESC LIMIT 100
)
SELECT user_id, count() FROM web_logs
WHERE user_id IN top_users
GROUP BY user_id;

CTE는 기본적으로 참조하는 곳마다 다시 계산됩니다. 결과를 한 번만 만들어 재사용하고 싶으면 임시 테이블이나 IN (서브쿼리)로 바꾸는 쪽이 예측하기 쉽습니다.

LIMIT BY, LIMIT WITH TIES

그룹마다 상위 N개를 뽑을 때 윈도우 함수 없이 LIMIT n BY 컬럼으로 끝냅니다. 로그에서 사용자별 최근 3건, 카테고리별 매출 상위 5개 같은 질문에 딱 맞습니다.

SELECT user_id, event_time, url
FROM web_logs
ORDER BY user_id, event_time DESC
LIMIT 3 BY user_id;

LIMIT 10 WITH TIES는 10번째와 정렬값이 같은 행을 함께 돌려줍니다. 26.9부터는 정렬된 결과에서 조건으로 출력 구간을 정하는 LIMIT ... AFTER 조건, LIMIT ... UNTIL 조건도 생겼습니다. 로그를 특정 에러가 처음 나온 뒤부터, 다음 배포 시점까지 잘라 보는 식으로 씁니다.

SELECT 수식어 (EXCEPT, REPLACE, APPLY, COLUMNS)

컬럼이 수백 개인 넓은 테이블에서 유용한 확장입니다.

-- 민감 컬럼만 빼고 전부
SELECT * EXCEPT (email, phone) FROM users LIMIT 5;

-- 이름이 _ms 로 끝나는 컬럼에 같은 함수 적용
SELECT COLUMNS('_ms$') APPLY (quantile(0.95)) FROM requests;

-- 특정 컬럼만 표현식으로 바꿔 보기
SELECT * REPLACE (lower(country) AS country) FROM users LIMIT 5;

GROUP BY 확장

구문하는 일
GROUP BY ... WITH TOTALS결과 끝에 전체 합계 행을 따로 붙입니다
WITH ROLLUP, WITH CUBE상위 집계 행을 함께 만듭니다
GROUPING SETS ((a), (b), ())원하는 조합만 골라 집계합니다
GROUP BY ALLSELECT에서 집계가 아닌 컬럼을 모두 GROUP BY 키로 삼습니다

집계 함수와 컴비네이터는 집계 함수와 컴비네이터 문서에서 자세히 다룹니다.

타입이 엄격합니다

  • 정수끼리 /를 하면 Float64가 나옵니다. 정수 몫은 intDiv(a, b), 나머지는a % b 또는 modulo.
  • UInt8끼리 더해도 UInt16으로 커지므로 넘침은 드물지만, toUInt8()로 다시 좁히면 잘립니다.
  • 문자열과 숫자를 바로 비교하면 오류입니다. toInt64(s), toString(n)처럼 변환 함수를 명시합니다.
  • 변환 함수는 세 가지 변형이 있습니다. toInt64('abc')는 예외, toInt64OrNull은 NULL, toInt64OrZero는 0을 돌려줍니다. 외부 데이터를 다룰 때는 OrNull과 OrZero가 쿼리를 살립니다.
  • 날짜 리터럴은 문자열로 적어도 비교 상대가 Date 타입이면 자동 변환됩니다. WHERE event_date = '2026-10-21'.
SELECT
    7 / 2 AS a,            -- 3.5 (Float64)
    intDiv(7, 2) AS b,     -- 3
    toInt32OrZero('x') AS c,
    toTypeName(1 + 1) AS t -- UInt16

타입 선택은 데이터 타입 고르기에서 이어집니다.

NULL 다루기

ClickHouse는 NULL을 기본으로 쓰지 않습니다. Nullable(T)는 값 옆에 NULL 여부를 따로 저장해서 공간과 속도를 조금 잃고, 기본 키에는 쓸 수 없습니다. 그래서 운영에서는 없음을 0이나 빈 문자열로 두고 필요한 곳만 Nullable로 둡니다.

  • 집계 함수는 NULL을 건너뜁니다. count(col)은 NULL이 아닌 행 수, count()는 전체 행 수.
  • coalesce, ifNull, nullIf, assumeNotNull이 있습니다.
  • JOIN에서 짝이 없는 쪽은 기본값으로 채워집니다. NULL로 받고 싶으면 SETTINGS join_use_nulls = 1. JOIN 전략 참고.

조건식과 문자열

  • 조건은 if(cond, a, b), multiIf(c1, a, c2, b, else), 표준 CASE WHEN을 모두 지원합니다.
  • 문자열 연결은 concat(a, b) 또는 a || b. format('{}-{}', a, b)도 됩니다.
  • 문자열 비교는 바이트 단위라 대소문자를 구분합니다. 대소문자 무시는 lower()를 양쪽에 걸거나 ILIKE를 씁니다.
  • LIKE, match(정규식), position, splitByChar, extractAll이 자주 쓰입니다.

SETTINGS 절과 설정의 범위

쿼리 끝의 SETTINGS는 그 쿼리에만 적용됩니다. 세션 전체는 SET, 사용자 단위는 설정 프로필입니다.

SELECT count()
FROM web_logs
WHERE url LIKE '%checkout%'
SETTINGS max_threads = 16, max_memory_usage = 20000000000;
범위방법쓰는 때
쿼리SETTINGS ...특정 무거운 쿼리만 메모리 상한을 올릴 때
세션SET max_threads = 4클라이언트 세션 동안 반복 실험
사용자, 역할설정 프로필 (users.xml 또는 CREATE SETTINGS PROFILE)팀별 한도
서버config.xml서버 자원 자체

설정 체계는 서버 설정에서 정리합니다.

FORMAT 절

쿼리 끝에 FORMAT JSONEachRow처럼 출력 형식을 붙입니다. HTTP로 결과를 받거나 파일로 내보낼 때 바로 씁니다. 입력 형식은 INSERT INTO t FORMAT CSV로 지정합니다. 형식 목록은 입출력 포맷에 있습니다.

테이블을 만들 때 다른 점

CREATE TABLE web_logs
(
    event_time DateTime,
    event_date Date DEFAULT toDate(event_time),
    user_id    UInt64,
    url        String,
    status     UInt16,
    tags       Array(LowCardinality(String))
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (user_id, event_time);
  • ENGINE을 꼭 적습니다. 대부분은 MergeTree 계열입니다. MergeTree 엔진
  • ORDER BY가 정렬 키이자 기본 키입니다. 설계 요령은 정렬 키, 기본 키, 파티션.
  • DEFAULT, MATERIALIZED, ALIAS, EPHEMERAL 컬럼으로 파생 값을 저장하거나 계산합니다.
  • UPDATE, DELETE는 ALTER TABLE ... UPDATE, ALTER TABLE ... DELETE(뮤테이션) 또는 경량 삭제 DELETE FROM으로 하고, 비용이 큽니다. 삭제와 갱신

새 분석기

24.3부터 새 쿼리 분석기(analyzer)가 기본입니다. 서브쿼리 안의 별칭, 람다, JOIN의 USING, 집계 안의 별칭 처리가 표준에 가깝게 바뀌었습니다. 옛 문서나 블로그의 쿼리가 다르게 동작하면 분석기 차이일 확률이 높습니다. SETTINGS allow_experimental_analyzer = 0으로 옛 동작과 비교해 볼 수 있지만, 옛 분석기는 유지보수가 끝나가므로 쿼리를 새 분석기에 맞게 고치는 쪽이 맞습니다.

파이프라인 SQL

26.x에서 |> 연산자로 단계를 위에서 아래로 이어 쓰는 파이프 문법이 들어왔습니다. 탐색 쿼리를 한 단계씩 붙여 가며 확인하기 좋습니다.

FROM web_logs
|> WHERE event_date = today()
|> AGGREGATE count() AS c GROUP BY status
|> ORDER BY c DESC
|> LIMIT 5;

팁 팀 저장소에 두는 쿼리는 아직 표준 모양으로 쓰고, 파이프 문법은 탐색용으로 쓰는 편이 다른 도구(BI, ORM)와 호환이 쉽습니다.

자주 틀리는 것 체크리스트

  • count(*) 대신 count()도 됩니다. 둘 다 전체 행 수.
  • SELECT DISTINCT 대신 GROUP BY나 LIMIT 1 BY가 더 빠른 때가 많습니다.
  • 서브쿼리 결과를 IN에 넣을 때 분산 테이블이면 GLOBAL IN을 고려합니다. 복제와 샤딩
  • ORDER BY 없는 LIMIT는 순서를 보장하지 않습니다. 병렬 읽기라 매번 달라질 수 있습니다.
  • FINAL은 ReplacingMergeTree 등에서 머지 전 중복을 지우지만 비용이 큽니다. MergeTree 가족
  • 느리면 EXPLAIN부터. 쿼리 최적화

공식 문서

이 문서에서