함수 개요

함수 개요 (Overview)

ClickHouse에는 크게 두 종류의 함수가 있어요. 일반 함수(그냥 "함수"라고 부르는 것)와 집계 함수(aggregate function)예요. 이 둘은 완전히 다른 개념이에요. 일반 함수는 각 행에 개별적으로 적용되는 것처럼 동작해요(함수의 결과는 다른 행에 의존하지 않아요). 집계 함수는 여러 행의 값 집합을 축적해요(즉 전체 행 집합에 의존해요).

이 섹션에서는 일반 함수를 다뤄요. 집계 함수는 "집계 함수" 섹션을 참고해요.

참고: 세 번째 종류의 함수도 있어요 — 'arrayJoin' 함수가 여기 속해요. 그리고 테이블 함수도 별도로 언급할 수 있어요.

출처: 문서

본문

강한 타입 (Strong Typing)

표준 SQL과 달리 ClickHouse는 강한 타입을 사용해요. 즉 타입 사이의 암시적 변환을 하지 않아요. 각 함수는 특정 타입 집합에 대해 동작해요. 그래서 때로는 타입 변환 함수를 사용해야 해요.

공통 부분식 제거 (Common Subexpression Elimination)

쿼리에서 같은 AST(같은 기록 또는 같은 구문 파싱 결과)를 가진 모든 표현식은 동일한 값으로 간주돼요. 그런 표현식들은 합쳐져 한 번만 실행돼요. 동일한 서브쿼리도 이렇게 제거돼요.

결과의 타입 (Types of Results)

모든 함수는 결과로 단일 값을 반환해요(여러 값도, 값이 없는 것도 아니에요). 결과의 타입은 보통 값이 아니라 인자의 타입에 의해서만 결정돼요. 예외는 tupleElement 함수(a.N 연산자)와 toFixedString 함수예요.

상수 (Constants)

단순화를 위해 특정 함수는 어떤 인자에 대해서는 상수로만 동작할 수 있어요. 예를 들어 LIKE 연산자의 오른쪽 인자는 반드시 상수여야 해요. 거의 모든 함수는 상수 인자에 대해 상수를 반환해요. 예외는 난수를 생성하는 함수예요. now 함수는 실행 시점이 다른 쿼리에 대해 다른 값을 반환하지만, 상수성은 단일 쿼리 안에서만 중요하므로 결과는 상수로 간주돼요. 상수 표현식도 상수로 간주돼요(예: LIKE 연산자의 오른쪽 절반은 여러 상수로 구성할 수 있어요).

함수는 상수 인자와 비상수 인자에 대해 다른 방식으로 구현될 수 있어요(다른 코드가 실행돼요). 하지만 상수에 대한 결과와 같은 값만 담긴 실제 컬럼에 대한 결과는 서로 일치해야 해요.

NULL 처리 (NULL Processing)

함수는 다음 동작을 가져요:

  • 함수의 인자 중 최소 하나가 NULL이면 함수 결과도 NULL이에요.
  • 각 함수 설명에 개별적으로 명시된 특수 동작. ClickHouse 소스 코드에서 이 함수들은 UseDefaultImplementationForNulls=false를 가져요.

상수성 (Constancy)

함수는 인자 값을 바꿀 수 없어요 — 어떤 변경도 결과로 반환돼요. 따라서 개별 함수 계산의 결과는 쿼리에서 함수가 쓰인 순서에 의존하지 않아요.

고차 함수 (Higher-order functions)

-> 연산자와 lambda(params, expr) 함수

고차 함수는 함수형 인자로 람다 함수만 받을 수 있어요. 고차 함수에 람다 함수를 넘기려면 -> 연산자를 사용해요. 화살표 왼쪽에는 형식 매개변수(임의의 ID 하나, 또는 튜플 안의 여러 형식 매개변수 — 임의의 ID 여러 개)가 있어요. 화살표 오른쪽에는 이 형식 매개변수와 어떤 테이블 컬럼이든 사용할 수 있는 표현식이 있어요.

예시:

x -> 2 * x
str -> str != Referer

여러 인자를 받는 람다 함수도 고차 함수에 전달할 수 있어요. 이 경우 고차 함수에는 이 인자들이 대응할 동일한 길이의 배열 여러 개가 전달돼요.

일부 함수에서는 첫 번째 인자(람다 함수)를 생략할 수 있어요. 이 경우 동일 매핑(identical mapping)이 가정돼요.

베어 함수 이름을 람다로 (Bare function names as lambdas)

전체 람다 표현식을 쓰는 대신 함수 이름을 고차 함수에 직접 전달할 수 있어요. 함수 이름은 자동으로 동등한 람다 표현식으로 변환돼요.

예를 들어 다음 쌍들은 동등해요:

SELECT arrayMap(negate, [1, 2, 3]);            -- [-1, -2, -3]
SELECT arrayMap(x -> negate(x), [1, 2, 3]);    -- [-1, -2, -3]

SELECT arrayMap(plus, [1, 2, 3], [10, 20, 30]);            -- [11, 22, 33]
SELECT arrayMap((x, y) -> plus(x, y), [1, 2, 3], [10, 20, 30]); -- [11, 22, 33]

SELECT arrayFilter(isNotNull, [1, NULL, 3, NULL, 5]);            -- [1, 3, 5]
SELECT arrayFilter(x -> isNotNull(x), [1, NULL, 3, NULL, 5]);    -- [1, 3, 5]

SELECT arrayFold(plus, [1, 2, 3, 4, 5], toUInt64(0));                      -- 15
SELECT arrayFold((acc, x) -> plus(acc, x), [1, 2, 3, 4, 5], toUInt64(0));  -- 15

이것은 내장 함수, SQL UDF, 실행 가능 UDF(executable UDF), WebAssembly UDF에서 동작해요. 모호할 때는 컬럼·별칭 이름이 함수 이름보다 우선해요.

람다의 인자 수(arity)는 내부 함수에서 가져와요. 예를 들어 arrayMap(plus, ...)plus가 인자 두 개를 받으므로 arity 2를 사용해요. 그래서 튜플 요소가 람다 인자로 풀리는 arrayMap(plus, [(1, 10), (2, 20)]) 같은 튜플 입력에서도 동작해요.

가변 인자(variadic) 내부 함수(예: 어떤 개수의 인자도 받는 concat)의 경우 람다 arity는 배열 인자의 개수로 폴백돼요. 이는 arrayMap, arrayFilter, arrayFold 같은 고차 함수에 맞아요. 배열 외에 고정 비배열 매개변수도 받는 고차 함수 — 예를 들어 arrayPartialSort(f, limit, arr) — 에서는 베어 가변 함수 이름이 잘못된 arity를 만들 수 있어요. 그 경우 명시적 람다가 필요해요.

가변 인자 내부 함수는 튜플 입력을 자동으로 풀지도 않아요. 예를 들어 arrayMap(concat, [('a', 'b'), ('c', 'd')])는 단항 람다로 재작성되며, arrayMap((x, y) -> concat(x, y), [('a', 'b'), ('c', 'd')])와 동등하지 않아요. 튜플 요소를 가변 호출로 분해하려면 명시적 람다를 사용해요.

사용자 정의 함수 (UDFs)

ClickHouse는 사용자 정의 함수를 지원해요. UDFs를 참고해요.

더 알아보기 (Learn more)