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으로 암시적으로 캐스팅될 수 있어요.

UNIONVARCHAR로 캐스팅할 때 멤버 타입의 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 함수를 쓰는 게 좋아요.

유일한 예외는 UNIONVARCHAR로 캐스팅하는 경우예요. 이때 멤버들이 각자의 VARCHAR 캐스트를 모두 사용해요. 모든 게 VARCHAR로 캐스팅될 수 있으니 어떤 의미에선 "안전"하죠.

Union으로 캐스팅 (Casting to Unions)

어떤 타입이든 UNION 멤버 타입 중 하나로 암시적으로 캐스팅될 수 있다면, 항상 그 UNION으로 암시적으로 캐스팅될 수 있어요.

  • 후보가 여러 개면 내장 암시적 캐스팅 우선순위 규칙이 대상 타입을 결정해요. 예를 들어 FLOATUNION(i INTEGER, v VARCHAR) 캐스팅은 항상 FLOATINTEGER 멤버로 먼저 캐스팅한 뒤 VARCHAR로 해요.
  • 그래도 캐스팅이 모호하면, 즉 같은 암시적 캐스팅 우선순위를 가진 후보가 여러 개면 오류가 발생해요. 보통 UNION이 같은 타입의 멤버를 여러 개 담을 때 생겨요. 예를 들어 FLOATUNION(i INTEGER, num INTEGER)은 항상 모호해요.

그럼 같은 타입의 멤버를 여러 개 가진 UNION을 만들고 싶을 땐 어떻게 모호성을 없앨까요? 태그를 지정하는 키워드 인자를 받는 union_value 함수를 쓰면 돼요. 예를 들어 union_value(num := 2::INTEGER)는 태그가 numINTEGER 타입 단일 멤버를 가진 UNION을 만들어요. 그러면 이걸로 명시적(또는 아래에서 보듯 암시적!) UNIONUNION 캐스팅에서 모호성을 없앨 수 있어요. 예: 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) BC로 암시적 캐스팅 가능할 때
UNION(a A, b B, c C) UNION(a A, b B)
UNION(a A, b B) UNION(a A, b C) BC로 암시적 캐스팅 불가능할 때
UNION(A, B, D) UNION(A, B, C)

비교와 정렬 (Comparison and Sorting)

UNION 타입은 내부적으로 STRUCT 타입 위에 구현되므로, STRUCT와 같은 시맨틱으로 모든 비교 연산자뿐 아니라 WHEREHAVING 절에서도 사용할 수 있어요. "태그"는 항상 첫 번째 struct 항목으로 저장되므로, UNION 타입은 항상 "태그" 우선으로 비교·정렬돼요.

함수 (Functions)

Union 함수를 참고해요.

더 알아보기 (Learn more)