Пошук уроків, статей та іншого контенту
Дізнаєтеся, як створювати перелічувані типи для обмеження допустимих значень у стовпцях.
enum (перелічуваний тип) — це користувацький тип даних, який містить фіксований набір допустимих значень.
Наприклад, для статусу замовлення можна дозволити лише:
new;
processing;
shipped;
cancelled.
На відміну від звичайного text, стовпець типу enum не прийме довільний рядок.
Enum-тип створюється окремою командою:
CREATE TYPE order_status AS ENUM (
'new',
'processing',
'shipped',
'cancelled'
);Значення перелічуються в потрібному порядку. Цей порядок використовується під час порівняння та сортування значень.
Після створення тип можна вказати як тип стовпця:
CREATE TABLE orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_name text NOT NULL,
status order_status NOT NULL DEFAULT 'new',
created_at timestamptz NOT NULL DEFAULT now()
);Тепер у status можна записати лише одне зі значень типу order_status:
INSERT INTO orders (customer_name, status)
VALUES
('Олена', 'new'),
('Андрій', 'processing'),
('Марія', 'shipped');Спроба додати значення, якого немає в enum, завершиться помилкою:
INSERT INTO orders (customer_name, status)
VALUES ('Іван', 'delivered');PostgreSQL повідомить, що значення delivered не належить до типу order_status.
Enum перевіряє не лише допустимість значення, а й тип. Наприклад, порівняння зі стовпцем іншого типу може вимагати явного приведення:
SELECT *
FROM orders
WHERE status = 'new'::order_status;У простих випадках PostgreSQL сам виконує потрібне приведення рядкового літерала, але явне приведення корисне в неоднозначних виразах.
Наведений скрипт можна виконати в PostgreSQL. Видалення на початку потрібне лише для того, щоб приклад можна було запускати повторно.
DROP TABLE IF EXISTS orders;
DROP TYPE IF EXISTS order_status;
CREATE TYPE order_status AS ENUM (
'new',
'processing',
'shipped',
'cancelled'
);
CREATE TABLE orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_name text NOT NULL,
status order_status NOT NULL DEFAULT 'new'
);
INSERT INTO orders (customer_name, status)
VALUES
('Олена', 'new'),
('Андрій', 'processing'),
('Марія', 'shipped'),
('Іван', 'cancelled');
-- Змінюємо статус одного замовлення.
UPDATE orders
SET status = 'shipped'
WHERE customer_name = 'Олена';
-- Вибираємо активні замовлення.
SELECT id, customer_name, status
FROM orders
WHERE status IN ('new', 'processing')
ORDER BY id;
-- Додаємо нове допустиме значення до enum.
ALTER TYPE order_status
ADD VALUE 'returned' AFTER 'shipped';
-- Тепер це значення можна використовувати.
INSERT INTO orders (customer_name, status)
VALUES ('Софія', 'returned');
SELECT id, customer_name, status
FROM orders
ORDER BY status;У фінальному запиті значення сортуються відповідно до порядку, у якому їх оголошено:
new
processing
shipped
returned
cancelledПорядок значень enum має значення для операторів порівняння та сортування:
SELECT 'new'::order_status < 'shipped'::order_status AS result;Результат:
trueЦе працює тому, що new оголошено перед shipped.
Порядок enum не є алфавітним. Наприклад, у такому типі:
CREATE TYPE priority AS ENUM (
'low',
'medium',
'high'
);запит:
SELECT 'high'::priority > 'low'::priority AS result;поверне true, хоча порядок можна було б змінити незалежно від назв.
Порядок особливо важливий, коли enum використовується в ORDER BY:
SELECT *
FROM orders
ORDER BY status;Рядки будуть відсортовані не за текстовим алфавітом, а за порядком значень у типі.
Для додавання нового значення використовується ALTER TYPE:
ALTER TYPE order_status
ADD VALUE 'returned';За замовчуванням нове значення додається в кінець списку.
Його можна вставити перед або після конкретного значення:
ALTER TYPE order_status
ADD VALUE 'refunded' AFTER 'returned';або:
ALTER TYPE order_status
ADD VALUE 'awaiting_payment' BEFORE 'processing';Додане значення стає частиною типу та може використовуватися в усіх стовпцях, які мають цей тип.
Значення можна перейменувати:
ALTER TYPE order_status
RENAME VALUE 'cancelled' TO 'canceled';Це змінює саме значення типу в схемі та вже наявних рядках.
Перейменування потрібно узгодити з кодом застосунку, який може очікувати старий рядок cancelled.
Enum не призначений для частих змін набору значень. PostgreSQL не має простої команди для видалення окремого значення або довільної перестановки вже наявних значень.
Якщо потрібно повністю змінити набір або порядок, зазвичай створюють новий тип і переносять стовпці на нього. Це потребує обережної міграції, оскільки тип може використовуватися кількома таблицями, індексами, функціями або представленнями.
Тому перед створенням enum варто переконатися, що набір значень справді стабільний.
Enum можна використовувати у фільтрах, умовах і групуванні так само, як інші скалярні типи:
SELECT status, count(*)
FROM orders
GROUP BY status
ORDER BY status;Для перевірки кількох значень можна використовувати IN:
SELECT *
FROM orders
WHERE status IN ('new', 'processing');Для параметрів, які надходять із застосунку, інколи потрібне явне приведення:
SELECT *
FROM orders
WHERE status = $1::order_status;Тут $1 — параметр підготовленого запиту, який має містити текстове значення, наприклад processing.
CHECK-обмеженняПодібну перевірку можна реалізувати за допомогою text та CHECK:
CREATE TABLE orders_with_check (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
status text NOT NULL
CHECK (status IN ('new', 'processing', 'shipped', 'cancelled'))
);Обидва підходи обмежують допустимі значення, але мають різні властивості.
Enum:
створює окремий тип у схемі;
повторно використовується в різних таблицях;
не допускає значень поза переліком;
зберігає визначений порядок значень;
складніше змінюється після створення.
text із CHECK:
простіше змінити;
зручно використовувати, якщо перелік часто редагується;
не створює окремого типу;
не використовується автоматично в інших таблицях.
Enum доречний для невеликого стабільного набору значень, наприклад типів ролей або напрямків операції. Якщо значення часто додаються, видаляються, мають власні атрибути чи переклади, краще зберігати їх в окремій таблиці.
Щоб отримати інформацію про enum у PostgreSQL, можна звернутися до системного каталогу:
SELECT
t.typname AS type_name,
e.enumlabel AS value,
e.enumsortorder AS sort_order
FROM pg_type AS t
JOIN pg_enum AS e ON e.enumtypid = t.oid
WHERE t.typname = 'order_status'
ORDER BY e.enumsortorder;Запит покаже назву типу, його значення та порядок сортування.
INSERT INTO orders (customer_name, status)
VALUES ('Петро', 'done');Якщо done не додано до order_status, PostgreSQL відхилить операцію. Потрібно або використати наявне значення, або спочатку додати нове через ALTER TYPE.
У цьому визначенні:
CREATE TYPE order_status AS ENUM ('new', 'shipped');order_status — назва типу, а new і shipped — його значення.
Під час створення таблиці використовується назва типу:
CREATE TABLE orders (
status order_status NOT NULL
);А під час вставки — конкретне значення:
INSERT INTO orders (status)
VALUES ('new');Enum сортується в порядку оголошення значень, а не за алфавітом. Якщо порядок не відповідає бізнес-логіці, звичайне ORDER BY status може дати несподіваний результат.
Enum незручний для переліків, які постійно змінюються. Додавання значень підтримується, але видалення та зміна структури типу можуть вимагати складної міграції.
Не варто створювати окремий enum для кожної таблиці, якщо вони описують один і той самий перелік. Один спільний тип можна використовувати в кількох таблицях:
CREATE TABLE shipments (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
status order_status NOT NULL
);Водночас спільний тип потрібно змінювати з урахуванням усіх таблиць, які його використовують.
Enum — це користувацький PostgreSQL-тип із фіксованим набором значень.
Тип створюють через CREATE TYPE ... AS ENUM.
Enum можна використовувати як тип стовпця, параметрів і результатів запитів.
Значення сортуються в порядку їх оголошення.
Нові значення додають через ALTER TYPE ... ADD VALUE.
Значення можна перейменувати, але видалення та перестановка потребують складнішої міграції.
Enum найкраще підходить для невеликих і стабільних переліків.
Для часто змінюваних наборів краще розглянути CHECK або окрему таблицю.