타입 캐스팅

타입 캐스팅 (Typecasting)

타입 캐스팅은 어떤 데이터 타입의 값을 다른 데이터 타입에서 가장 가까운 값으로 변환하는 연산이에요. 다른 SQL 엔진처럼 DuckDB도 암시적(implicit) 캐스팅과 명시적(explicit) 캐스팅을 모두 지원해요.

출처: 문서

본문

명시적 캐스팅 (Explicit Casting)

명시적 캐스팅은 CAST 표현식으로 수행해요. 예를 들어 CAST(col AS VARCHAR) 또는 col::VARCHAR는 열 colVARCHAR로 명시적으로 캐스팅해요. 자세한 내용은 cast 페이지를 참고해요.

암시적 캐스팅 (Implicit Casting)

많은 상황에서 시스템이 캐스트를 스스로 추가해요. 이를 암시적 캐스팅이라고 해요. 예를 들어 함수를 호출할 때 인자가 함수의 타입과 일치하지 않지만 필요한 타입으로 캐스팅될 수 있는 경우에 일어나요.

암시적 캐스트는 제한된 타입 조합에만 추가될 수 있고, 일반적으로 캐스트가 실패할 수 없을 때만 가능해요. 예를 들어 INTEGER에서 DOUBLE로의 암시적 캐스트는 추가될 수 있지만, DOUBLE에서 INTEGER로는 안 돼요.

sin(DOUBLE) 함수를 생각해 보죠. 이 함수는 DOUBLE 타입 열을 입력 인자로 받지만, sin(1)처럼 정수로도 호출할 수 있어요. 정수는 sin 함수에 전달되기 전에 double로 변환돼요.

팁 어떤 타입이 다른 타입으로 암시적으로 캐스팅될 수 있는지 확인하려면 can_cast_implicitly 함수를 사용해요.

조합 캐스팅 (Combination Casting)

다른 타입의 값들을 지정되지 않은 공통 부모 타입으로 결합해야 할 때, 시스템은 자동으로 선택한 부모 타입으로 암시적 캐스트를 수행해요. 예를 들어 list_value(1::INT64, 1::UINT64)INT128[] 타입의 리스트를 만들어요. 이 상황에서 수행되는 암시적 캐스트는 일반적인 암시적 캐스트보다 더 관대할 때가 있어요. 예를 들어 BOOL 값은 일반적인 암시적 캐스트로는 불가능하지만, INT로 캐스팅될 수 있어요(true1, false0으로 매핑).

조합 캐스팅은 비교(= / < / >), 집합 연산(UNION / EXCEPT / INTERSECT), 중첩 타입 생성자(list_value / [...] / MAP)에서 발생해요.

캐스팅 연산 행렬 (Casting Operations Matrix)

특정 데이터 타입의 값이 항상 임의의 대상 타입으로 캐스팅될 수 있는 건 아니에요. 유일한 예외는 NULL 값인데, 이는 항상 타입 간에 변환될 수 있어요. 다음 행렬은 어떤 변환이 지원되는지 설명해요. 암시적 캐스팅이 허용되면 명시적 캐스팅도 가능함을 의미해요.

Typecasting matrix

소스와 대상 데이터 타입 기준으로 캐스팅 연산이 지원된다 해도, 캐스트 연산이 런타임에 반드시 성공하리란 보장은 없어요.

Deprecated 0.10.0 버전 이전에는 DuckDB가 함수 바인딩 중 어떤 타입이든 VARCHAR로 암시적으로 캐스팅하는 것을 허용했어요. 0.10.0 버전은 더 이상 VARCHAR로의 암시적 캐스팅을 허용하지 않는 파괴적 변경을 도입했어요. old_implicit_casting 설정 옵션으로 예전 동작으로 되돌릴 수 있어요. 다만 이 플래그는 향후 deprecated 될 예정이에요.

손실 있는 캐스트 (Lossy Casts)

정밀도 손실을 초래하는 캐스팅 연산은 허용돼요. 예를 들어 DECIMAL, FLOAT, DOUBLE처럼 소수 자릿수가 있는 숫자 타입을 INTEGERBIGINT 같은 정수 타입으로 명시적으로 캐스팅할 수 있어요. 숫자는 반올림돼요.

SELECT CAST(3.1 AS INTEGER);  -- 3
SELECT CAST(3.5 AS INTEGER);  -- 4
SELECT CAST(-1.7 AS INTEGER); -- -2

적용되는 반올림 규칙은 NUMERIC type 페이지에서 찾을 수 있어요.

오버플로 (Overflows)

값 오버플로를 초래하는 캐스팅 연산은 오류를 던져요. 예를 들어 값 999TINYINT 데이터 타입으로 표현하기엔 너무 커요. 따라서 그 값을 해당 타입으로 캐스팅하려 하면 런타임 오류가 나요:

SELECT CAST(999 AS TINYINT);
Conversion Error:
Type INT32 with value 999 can't be cast because the value is out of range for the destination type INT8

INTEGER에서 TINYINT로의 캐스팅 연산은 지원되지만, 이 특정 값에 대해서는 불가능해요. TRY_CAST를 사용하면 오류를 던지는 대신 값을 NULL로 변환할 수 있어요.

Varchar

VARCHAR 타입은 보편적 대상 역할을 해요. 어떤 타입의 어떤 값이든 항상 VARCHAR 타입으로 캐스팅될 수 있어요. 이 타입은 셸에서 값을 표시할 때도 사용돼요.

SELECT CAST(42.5 AS VARCHAR);

VARCHAR에서 다른 데이터 타입으로의 캐스팅도 지원되지만, DuckDB가 제공된 텍스트를 대상 데이터 타입으로 파싱·변환할 수 없으면 런타임에 오류를 낼 수 있어요.

SELECT CAST('NotANumber' AS INTEGER);

일반적으로 VARCHAR로의 캐스팅은 손실 없는 연산이고, 텍스트로 변환된 뒤 어떤 타입이든 원래 타입으로 캐스팅해 되돌릴 수 있어요.

SELECT CAST(CAST([1, 2, 3] AS VARCHAR) AS INTEGER[]);

리터럴 타입 (Literal Types)

정수 리터럴(예: 42)과 문자열 리터럴(예: 'string')은 특별한 암시적 캐스팅 규칙이 있어요. 자세한 내용은 literal types 페이지를 참고해요.

리스트 / 배열 (Lists / Arrays)

리스트는 같은 캐스팅 규칙을 사용해 다른 리스트로 명시적으로 캐스팅될 수 있어요. 캐스트는 리스트의 자식에 적용돼요. 예를 들어 INTEGER[] 리스트를 VARCHAR[] 리스트로 변환하면, 자식 INTEGER 요소가 각각 VARCHAR로 캐스팅되고 새 리스트가 구성돼요.

SELECT CAST([1, 2, 3] AS VARCHAR[]);

배열 (Arrays)

배열은 리스트와 같은 캐스팅 규칙을 따라요. 추가로 배열은 같은 타입의 리스트로 암시적으로 캐스팅될 수 있어요. 예를 들어 INTEGER[3] 배열은 INTEGER[] 리스트로 암시적으로 캐스팅될 수 있어요.

구조체 (Structs)

구조체는 필드를 하나 이상 공유하는 한 다른 구조체로 캐스팅될 수 있어요.

이 요구 사항의 근거는 의도치 않은 오류를 피하기 위해서예요. 두 구조체가 공통 필드가 하나도 없다면, 그 캐스팅은 아마 의도된 게 아닐 거예요.

SELECT CAST({'a': 42} AS STRUCT(a VARCHAR));

대상 구조체에 존재하지만 소스 구조체에는 없는 필드는 NULL로 기본값 처리돼요.

SELECT CAST({'a': 42} AS STRUCT(a VARCHAR, b VARCHAR));

소스 구조체에만 존재하는 필드는 무시돼요.

SELECT CAST({'a': 42, 'b': 43} AS STRUCT(a VARCHAR));

구조체의 이름이 다른 순서일 수도 있어요. 구조체의 필드는 구조체 이름에 따라 재배열돼요.

SELECT CAST({'a': 42, 'b': 84} AS STRUCT(b VARCHAR, a VARCHAR));

조합 캐스팅의 경우, 결과 구조체의 필드는 입력 구조체들의 모든 필드의 상위집합(superset)이 돼요. 이 논리는 잠재적으로 중첩된 구조체에도 재귀적으로 적용돼요.

SELECT {'outer1': {'inner1': 42, 'inner2': 42}} AS c
UNION
SELECT {'outer1': {'inner2': 'hello', 'inner3': 'world'}, 'outer2': '100'} AS c;
SELECT [{'a': 42}, {'b': 84}];

Unions

Union 캐스팅 규칙은 UNION type 페이지에서 찾을 수 있어요.

더 알아보기 (Learn more)