UNION 타입
UNION 타입 (Union Type)
UNION 타입(SQL의 UNION 연산자와 혼동하지 마세요)은 여러 "대안" 값 중 하나를 담을 수 있는 중첩 타입이에요. C의 union과 비슷하죠. 가장 큰 차이는 이 UNION 타입이 *태그된 유니온(tagged union)*이라서, 내부 값 자체가 null이어도 현재 어떤 대안을 담고 있는지 알려 주는 판별자 "태그"를 항상 갖는다는 점이에요. 그래서 UNION 타입은 C++17의 std::variant, Rust의 Enum, 또는 대부분의 함수형 언어에 있는 "sum type"에 더 가까워요.
UNION 타입은 반드시 멤버가 하나 이상 있어야 하고, 같은 타입의 멤버를 여러 개 담을 수는 있지만 태그 이름은 고유해야 해요. UNION 타입은 최대 256개의 멤버를 가질 수 있어요.
내부적으로 UNION 타입은 STRUCT 타입 위에 구현되어 있고, "태그"를 첫 번째 항목으로 유지해요.
UNION 값은 union_value(tag := expr) 함수 또는 멤버 타입에서의 캐스팅으로 만들 수 있어요.
출처: 문서
본문
예시 (Example)
UNION 열이 있는 테이블을 만들어 볼게요:
CREATE TABLE tbl1 (u UNION(num INTEGER, str VARCHAR));
INSERT INTO tbl1 VALUES (1), ('two'), (union_value(str := 'three'));
어떤 타입이든 그 타입을 담는 UNION으로 암시적으로 캐스팅할 수 있어요. 또 소스 UNION 멤버가 대상 멤버의 부분집합이면(모호하지 않을 때) 어떤 UNION이든 다른 UNION으로 암시적으로 캐스팅될 수 있어요.
UNION은 VARCHAR로 캐스팅할 때 멤버 타입의 VARCHAR 캐스트 함수를 사용해요:
SELECT u FROM tbl1;
| u |
|---|
| 1 |
| two |
| three |
모든 str 멤버를 골라 볼게요:
SELECT union_extract(u, 'str') AS str
FROM tbl1;
| str |
|---|
| NULL |
| two |
| three |
혹은 STRUCT처럼 '점 표기법(dot syntax)'을 사용할 수도 있어요.
SELECT u.str
FROM tbl1;
| str |
|---|
| NULL |
| two |
| three |
UNION에서 현재 활성 태그를 ENUM으로 골라 볼게요.
SELECT union_tag(u) AS t
FROM tbl1;
| t |
|---|
| num |
| str |
| str |
Union 캐스팅 (Union Casts)
다른 중첩 타입과 달리 UNION은 멤버를 "부분 타입(subtype)"처럼 다루는 자연스럽고 덜 방해적인 사용을 돕기 위해 일련의 암시적 캐스팅을 허용해요. 다만 이 캐스팅들은 모호성을 피하고 정보 손실로 이어질 수 있는 캐스팅을 피한다는 두 가지 원칙으로 설계됐어요. 덕분에 UNION이 완전히 "투명"해지지는 않지만, UNION 타입이 멤버들과 "슈퍼타입(supertype)" 관계를 가질 수는 있어요.
즉 UNION 타입은 일반적으로 멤버 타입 중 어느 것으로도 암시적으로 캐스팅될 수 없어요. 대상 타입과 일치하지 않는 다른 멤버들의 정보가 "손실"되기 때문이에요. UNION을 멤버 중 하나로 강제하고 싶다면, 명시적으로 union_extract 함수를 쓰는 게 좋아요.
유일한 예외는 UNION을 VARCHAR로 캐스팅하는 경우예요. 이때 멤버들이 각자의 VARCHAR 캐스트를 모두 사용해요. 모든 게 VARCHAR로 캐스팅될 수 있으니 어떤 의미에선 "안전"하죠.
Union으로 캐스팅 (Casting to Unions)
어떤 타입이든 UNION 멤버 타입 중 하나로 암시적으로 캐스팅될 수 있다면, 항상 그 UNION으로 암시적으로 캐스팅될 수 있어요.
- 후보가 여러 개면 내장 암시적 캐스팅 우선순위 규칙이 대상 타입을 결정해요. 예를 들어
FLOAT→UNION(i INTEGER, v VARCHAR)캐스팅은 항상FLOAT를INTEGER멤버로 먼저 캐스팅한 뒤VARCHAR로 해요. - 그래도 캐스팅이 모호하면, 즉 같은 암시적 캐스팅 우선순위를 가진 후보가 여러 개면 오류가 발생해요. 보통
UNION이 같은 타입의 멤버를 여러 개 담을 때 생겨요. 예를 들어FLOAT→UNION(i INTEGER, num INTEGER)은 항상 모호해요.
그럼 같은 타입의 멤버를 여러 개 가진 UNION을 만들고 싶을 땐 어떻게 모호성을 없앨까요? 태그를 지정하는 키워드 인자를 받는 union_value 함수를 쓰면 돼요. 예를 들어 union_value(num := 2::INTEGER)는 태그가 num인 INTEGER 타입 단일 멤버를 가진 UNION을 만들어요. 그러면 이걸로 명시적(또는 아래에서 보듯 암시적!) UNION → UNION 캐스팅에서 모호성을 없앨 수 있어요. 예: CAST(union_value(b := 2) AS UNION(a INTEGER, b INTEGER)).
Union 사이의 캐스팅 (Casting between Unions)
소스 타입이 대상 타입의 "부분집합"이면 UNION 타입끼리 캐스팅할 수 있어요. 즉 소스 UNION의 모든 태그가 대상 UNION에 존재해야 하고, 일치하는 태그들의 모든 타입이 소스와 대상 사이에서 암시적으로 캐스팅 가능해야 해요. 결국 UNION 타입은 멤버에 대해 공변(covariant)이라는 뜻이에요.
| Ok | 소스 (Source) | 대상 (Target) | 비고 (Comments) |
|---|---|---|---|
| ✅ | UNION(a A, b B) |
UNION(a A, b B, c C) |
|
| ✅ | UNION(a A, b B) |
UNION(a A, b C) |
B가 C로 암시적 캐스팅 가능할 때 |
| ❌ | UNION(a A, b B, c C) |
UNION(a A, b B) |
|
| ❌ | UNION(a A, b B) |
UNION(a A, b C) |
B가 C로 암시적 캐스팅 불가능할 때 |
| ❌ | UNION(A, B, D) |
UNION(A, B, C) |
비교와 정렬 (Comparison and Sorting)
UNION 타입은 내부적으로 STRUCT 타입 위에 구현되므로, STRUCT와 같은 시맨틱으로 모든 비교 연산자뿐 아니라 WHERE와 HAVING 절에서도 사용할 수 있어요. "태그"는 항상 첫 번째 struct 항목으로 저장되므로, UNION 타입은 항상 "태그" 우선으로 비교·정렬돼요.
함수 (Functions)
Union 함수를 참고해요.