Enum 데이터 타입
Enum 데이터 타입 (Enum Data Type)
Enum 타입은 컬럼의 모든 가능한 고유 값을 담는 딕셔너리(dictionary) 구조를 나타내는 타입이에요. 예를 들어 요일을 저장하는 컬럼을 모든 가능한 요일을 담는 enum으로 만들 수 있어요. 저장 공간도 아끼고 쿼리도 빨라지는 효과가 있답니다.
출처: 문서
본문
| 이름 | 설명 |
|---|---|
ENUM |
컬럼의 모든 가능한 문자열 값을 나타내는 딕셔너리 |
enum 타입은 컬럼의 모든 가능한 고유 값(unique values)을 담는 딕셔너리 데이터 구조를 나타내요. 예를 들어 요일을 저장하는 컬럼은 모든 가능한 요일을 담는 enum이 될 수 있어요. Enum은 카디널리티가 낮은(즉, 고유 값이 적은) 문자열 컬럼에 특히 유용해요. 컬럼이 enum 딕셔너리의 문자열에 대한 숫자 참조만 저장하기 때문에, 디스크 저장 공간을 엄청나게 아낄 수 있고 쿼리 성능도 더 빨라져요.
Enum 만들기 (Creating Enums)
하드코딩된 값으로 enum을 만들 수 있어요:
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
-- 이 명령문은 enum이 NULL 값을 가질 수 없어 실패해요:
-- CREATE TYPE mood AS ENUM ('sad', NULL);
-- 이 명령문은 enum 값이 고유해야 해서 실패해요:
-- CREATE TYPE mood AS ENUM ('sad', 'sad');
특정 스키마에 enum을 만들 수도 있어요:
CREATE SCHEMA my_schema;
CREATE TYPE my_schema.mood AS ENUM ('sad', 'ok', 'happy');
익명(anonymous) enum은 캐스팅 중에 즉석으로 만들 수 있어요:
SELECT 'clubs'::ENUM ('spades', 'hearts', 'diamonds', 'clubs');
단일 VARCHAR 컬럼을 반환하는 SELECT 문으로도 enum을 만들 수 있어요. select 문의 값 집합은 자동으로 중복 제거되고, NULL 값은 무시돼요:
CREATE TYPE region AS ENUM (SELECT region FROM sales_data);
파일에서 데이터를 가져온다면, 가져오기 전에 VARCHAR 컬럼용 enum을 만들 수 있어요:
CREATE TYPE region AS ENUM (SELECT region FROM 'sales_data.csv');
CREATE TABLE sales_data (amount INTEGER, region region);
COPY sales_data FROM 'sales_data.csv';
Enum 사용하기 (Using Enums)
Enum 값은 대소문자를 구분해요. 그래서 'maltese'와 'Maltese'는 서로 다른 값으로 취급돼요:
CREATE TYPE breed AS ENUM ('maltese', 'Maltese');
-- false를 반환해요
SELECT 'maltese'::breed = 'Maltese'::breed;
-- 에러가 발생해요
SELECT 'MALTESE'::breed;
enum이 만들어지면 표준 내장 타입이 쓰이는 어디든 사용할 수 있어요. 예를 들어 enum을 참조하는 컬럼이 있는 테이블을 만들 수 있어요.
CREATE TABLE person (
name TEXT,
current_mood mood
);
INSERT INTO person VALUES
('Pedro', 'happy'),
('Mark', NULL),
('Pagliacci', 'sad'),
('Mr. Mackey', 'ok');
mood 타입에 quackity-quack 값이 없으므로 다음 쿼리는 실패해요.
INSERT INTO person VALUES ('Hannes', 'quackity-quack');
Enum vs. 문자열 (Enums vs. Strings)
DuckDB enum은 필요할 때마다 자동으로 VARCHAR 타입으로 캐스팅돼요. 이 특성 덕분에 서로 다른 enum끼리, 또는 enum과 VARCHAR 컬럼 간의 비교가 가능해요.
또한 어떤 VARCHAR 함수에서도 enum을 사용할 수 있어요. 예를 들어:
SELECT current_mood, regexp_matches(current_mood, '.*a.*') AS contains_a FROM person;
| current_mood | contains_a |
|---|---|
| happy | true |
| NULL | NULL |
| sad | true |
| ok | false |
두 개의 다른 enum 타입을 비교할 때 DuckDB는 둘 다 문자열로 캐스팅한 뒤 문자열 비교를 수행해요:
CREATE TYPE new_mood AS ENUM ('happy', 'anxious');
SELECT * FROM person
WHERE current_mood = 'happy'::new_mood;
-- `WHERE current_mood::VARCHAR = 'happy'::VARCHAR`와 동일
| name | current_mood |
|---|---|
| Pedro | happy |
enum을 VARCHAR와 비교할 때 DuckDB는 enum을 VARCHAR로 캐스팅한 뒤 문자열 비교를 수행해요:
SELECT * FROM person
WHERE current_mood = name;
-- `WHERE current_mood::VARCHAR = name`와 동일
-- 반환되는 행 없음
상수 문자열과 비교할 때 DuckDB는 최적화를 수행해서 try_cast(⟨constant string⟩, enum_type){:.language-sql .highlight}로 처리해요. 그래서 물리적으로는 문자열 비교 대신 정수 비교를 수행해요(논리적으로는 여전히 문자열 비교예요):
SELECT * FROM person
WHERE current_mood = 'sad';
-- `WHERE current_mood::VARCHAR = 'sad'`와 동일
| name | current_mood |
|---|---|
| Pagliacci | sad |
경고 (Warning) 즉, 일치하지 않는(임의의) 문자열과 비교하면 항상
false가 되고(에러가 나지 않고) 결과가 반환돼요:
SELECT * FROM person
WHERE current_mood = 'bogus';
-- `WHERE current_mood::VARCHAR = 'bogus'`와 동일
-- 반환되는 행 없음
타입 안전성을 강제하고 싶다면 enum으로 명시적으로 캐스팅하면 돼요:
SELECT * FROM person
WHERE current_mood = 'bogus'::mood;
-- Conversion Error: Could not convert string 'bogus' to UINT8
Enum의 순서 (Ordering of Enums)
Enum 값은 enum 정의에서의 순서에 따라 정렬돼요. 예를 들어:
CREATE TYPE priority AS ENUM ('low', 'medium', 'high');
SELECT 'low'::priority < 'high'::priority AS comp;
-- 참고: 'low'::VARCHAR < 'high'::VARCHAR는 false예요!
| comp |
|---|
| true |
SELECT unnest(['medium'::priority, 'high'::priority, 'low'::priority]) AS m
ORDER BY m;
| m |
|---|
| low |
| medium |
| high |
경고 (Warning) enum을 non-enum(예:
VARCHAR나 다른 enum 타입)과 비교하면, enum은 먼저 문자열로 캐스팅되고(앞 절에서 설명한 대로), 비교는 문자열과 마찬가지로 사전순(lexicographically)으로 수행돼요:
CREATE TABLE tasks (name TEXT, priority_level priority);
INSERT INTO tasks VALUES ('a', 'low'), ('b', 'medium'), ('c', 'high');
-- 경고!
-- `WHERE priority_level::VARCHAR >= 'medium'`와 동일
SELECT * FROM tasks
WHERE priority_level >= 'medium';
-- 'high' 우선순위 작업을 놓쳐요!
| name | priority_level |
|---|---|
| b | medium |
그래서 예를 들어 "medium 이상의 모든 우선순위를 가져오고 싶다"면 enum 타입으로 명시적으로 캐스팅하면 돼요:
SELECT * FROM tasks
WHERE priority_level >= 'medium'::priority;
| name | priority_level |
|---|---|
| b | medium |
| c | high |
함수 (Functions)
Enum 함수를 참고해요.
예를 들어 enum_range 함수로 mood enum에서 사용 가능한 값을 보여줄 수 있어요:
SELECT enum_range(NULL::mood) AS my_enum_range;
| my_enum_range |
|---|
[sad, ok, happy] |
Enum 제거 (Enum Removal)
Enum 타입은 카탈로그에 저장되고, 이를 사용하는 각 테이블에 카탈로그 의존성이 추가돼요. 다음 명령으로 카탈로그에서 enum을 삭제할 수 있어요:
DROP TYPE ⟨enum_name⟩;
현재는 테이블에서 사용되는 enum을 테이블에 영향을 주지 않고 삭제할 수 있어요.
경고 (Warning) enum 제거 기능의 이 동작은 변경될 수 있어요. 향후 릴리스에서는 enum을 삭제하기 전에 의존 컬럼을 모두 제거해야 하거나, 추가
CASCADE파라미터로 enum을 삭제해야 할 것으로 예상돼요.