跳到主要内容

Enum 数据类型

名称描述
ENUM表示某列所有可能字符串值的字典

Enum 类型表示一种字典数据结构,包含某列所有可能且唯一的值。例如,存储一周中星期几的列可以使用一个包含所有可能星期值的枚举。对于低基数字符串列(即不同值较少)而言,枚举特别有价值。因为列中只存储对枚举字典中字符串的数值引用,从而显著节省磁盘存储并提升查询性能。

创建 Enum

你可以使用硬编码值创建枚举:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
-- This statement will fail since enums cannot hold NULL values:
-- CREATE TYPE mood AS ENUM ('sad', NULL);
-- This statement will fail since enum values must be unique:
-- CREATE TYPE mood AS ENUM ('sad', 'sad');

你可以在特定 schema 中创建枚举:

CREATE SCHEMA my_schema;
CREATE TYPE my_schema.mood AS ENUM ('sad', 'ok', 'happy');

也可以在类型转换时即时创建匿名枚举:

SELECT 'clubs'::ENUM ('spades', 'hearts', 'diamonds', 'clubs');

你还可以通过返回单列 VARCHARSELECT 语句创建枚举。 来自查询的值集合会自动去重, 并且会忽略 NULL 值:

CREATE TYPE region AS ENUM (SELECT region FROM sales_data);

如果你要从文件导入数据,可以在导入前先为 VARCHAR 列创建枚举:

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

枚举值区分大小写,因此 'maltese''Maltese' 会被视为不同值:

CREATE TYPE breed AS ENUM ('maltese', 'Maltese');
-- Will return false
SELECT 'maltese'::breed = 'Maltese'::breed;
-- Will error
SELECT 'MALTESE'::breed;

枚举创建后,可在任何可使用标准内置类型的位置使用。 例如,我们可以创建一个引用该枚举的表列。

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 与字符串

Goose 在需要时会自动将枚举转换为 VARCHAR 类型。 这一特性允许比较不同枚举,或比较枚举与 VARCHAR 列。

它也允许在任意 VARCHAR 函数中使用枚举。例如:

SELECT current_mood, regexp_matches(current_mood, '.*a.*') AS contains_a FROM person;
current_moodcontains_a
happytrue
NULLNULL
sadtrue
okfalse

比较两个不同的枚举类型时,Goose 会先将两者转换为字符串,再执行字符串比较:

CREATE TYPE new_mood AS ENUM ('happy', 'anxious');
SELECT * FROM person
WHERE current_mood = 'happy'::new_mood;
-- Equivalent to `WHERE current_mood::VARCHAR = 'happy'::VARCHAR`
namecurrent_mood
Pedrohappy

比较枚举与 VARCHAR 时,Goose 会将枚举转为 VARCHAR,并执行字符串比较:

SELECT * FROM person
WHERE current_mood = name;
-- Equivalent to `WHERE current_mood::VARCHAR = name`
-- No rows returned

与常量字符串比较时,Goose 会进行优化, 并执行 try_cast(⟨constant string⟩, enum_type), 使物理执行变成整数比较而不是字符串比较 (但逻辑上仍然是字符串比较):

SELECT * FROM person
WHERE current_mood = 'sad';
-- Equivalent to `WHERE current_mood::VARCHAR = 'sad'`
namecurrent_mood
Pagliaccisad

Warning 这意味着与任意(不等价)字符串比较时总会得到 false(不会报错):

SELECT * FROM person
WHERE current_mood = 'bogus';
-- Equivalent to `WHERE current_mood::VARCHAR = 'bogus'`
-- No rows returned

如果你希望强制类型安全,请显式转换为对应枚举类型:

SELECT * FROM person
WHERE current_mood = 'bogus'::mood;
-- Conversion Error: Could not convert string 'bogus' to UINT8

Enum 的排序

枚举值按其在枚举定义中的顺序排序。例如:

CREATE TYPE priority AS ENUM ('low', 'medium', 'high');
SELECT 'low'::priority < 'high'::priority AS comp;
-- note that 'low'::VARCHAR < 'high'::VARCHAR is false!
comp
true
SELECT unnest(['medium'::priority, 'high'::priority, 'low'::priority]) AS m
ORDER BY m;
m
low
medium
high

Warning 如果你将枚举与非枚举比较(例如 VARCHAR 或不同的枚举类型), 枚举会先被转换为字符串(如上一节所述), 然后按字符串字典序进行比较:

CREATE TABLE tasks (name TEXT, priority_level priority);
INSERT INTO tasks VALUES ('a', 'low'), ('b', 'medium'), ('c', 'high');
-- WARNING!
-- Equivalent to `WHERE priority_level::VARCHAR >= 'medium'`
SELECT * FROM tasks
WHERE priority_level >= 'medium';
-- Misses the 'high' priority task!
namepriority_level
bmedium

因此,如果你想例如“获取所有大于等于 medium 的优先级”,请显式转换为枚举类型:

SELECT * FROM tasks
WHERE priority_level >= 'medium'::priority;
namepriority_level
bmedium
chigh

函数

参见枚举函数

例如,使用 enum_range 函数展示 moods 枚举中的可用值:

SELECT enum_range(NULL::moods) AS my_enum_range;
my_enum_range
[sad, ok, happy]

删除 Enum

枚举类型存储在 catalog 中,并且每个使用该枚举的表都会建立 catalog 依赖。可以通过以下命令从 catalog 中删除枚举:

DROP TYPE ⟨enum_name⟩;

目前,即使表仍在使用某个枚举,也可以删除该枚举,而不会影响这些表。

Warning 枚举删除功能的这一行为未来可能变化。后续版本预计会要求先删除依赖列,或在删除枚举时附加 CASCADE 参数。