Массивы, диапазоны, enum, составные типы

Рейтинг: 80.6% · 16 голосов
Подробный курс по PostgreSQL: SQL по демобазе, типы данных и JSONB, индексы, транзакции и MVCC, VACUUM, EXPLAIN и планировщик, роли и привилегии, репликация, бэкап, настройка под нагрузку и под 1С. Актуально на 2026.
Ответить
Аватара пользователя
Egor_DBA
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Массивы, диапазоны, enum, составные типы

Сообщение Egor_DBA »

Оглавление курса (47)
  1. Что такое PostgreSQL: история, философия, где применяется
  2. Урок 1. Что нового в PostgreSQL 15 и далее (15 -> 16 -> 17)
  3. Установка и первый запуск: Linux, Windows, Docker
  4. psql детально: метакоманды и работа в консоли
  5. Демобаза Авиаперевозки Postgres Pro: структура и развёртывание
  6. SELECT: проекция, фильтрация, сортировка
  7. Транзакции и ACID
  8. Уровни изоляции транзакций: Read Committed, Repeatable Read, Serializable
  9. Соединения таблиц (JOIN)
  10. Агрегация: GROUP BY, HAVING, GROUPING SETS
  11. Подзапросы и CTE (WITH), рекурсия
  12. Оконные функции
  13. Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE
  14. Числовые и булевы типы, NULL и трёхзначная логика
  15. Строки, текст, дата и время, таймзоны
  16. Массивы, диапазоны, enum, составные типы (вы здесь)
  17. JSON и JSONB: операторы и индексация
  18. DDL: CREATE TABLE, схемы, ALTER
  19. Ограничения целостности
  20. Представления и материализованные представления
  21. Последовательности, IDENTITY, генерируемые столбцы
  22. Индексы: B-tree и когда индекс не используется
  23. Типы индексов: Hash, GiST, SP-GiST, GIN, BRIN
  24. Продвинутые индексы: частичные, по выражению, покрывающие
  25. Обслуживание индексов и таблиц: bloat, REINDEX, CONCURRENTLY
  26. Полнотекстовый поиск
  27. Роли и пользователи
  28. Привилегии: GRANT/REVOKE
  29. Row-Level Security и безопасность данных
  30. Подключение приложения: pg_hba.conf, SSL, пулы соединений
  31. Серверное программирование: функции и процедуры
  32. Триггеры и события
  33. Архитектура PostgreSQL: процессы и память
  34. MVCC: версии строк и видимость
  35. VACUUM, autovacuum, freeze и wraparound
  36. WAL, контрольные точки и долговечность
  37. Блокировки и взаимоблокировки
  38. Планировщик запросов
  39. EXPLAIN и EXPLAIN ANALYZE: чтение планов
  40. Конфигурация сервера: ключевые параметры
  41. Настройка под нагрузку и под 1С
  42. Параллельные запросы и партиционирование
  43. Резервное копирование и восстановление
  44. Репликация: потоковая и hot standby
  45. Логическая репликация и кластерные решения
  46. Внешние данные, расширения и сертификация
  47. Разбор планов запросов на explain.tensor.ru: глубокое чтение EXPLAIN ANALYZE
Урок 15. Массивы, диапазоны, enum, составные типы

Реляционная модель учит раскладывать данные по плоским колонкам, но в postgresql есть целое семейство составных типов данных, которые позволяют хранить в одной ячейке несколько значений сразу. Массив тегов, интервал дат брони, фиксированный список статусов, упакованный адрес - все это можно описать одним столбцом, не плодя отдельные справочники. В этом уроке разберем четыре таких инструмента: массивы, диапазоны (range), перечисления (enum) и составные (row) типы. Главное - понять не только синтаксис, но и когда такой тип реально упрощает схему, а когда он маскирует плохой дизайн и тянет вас в болото.

Изображение

Как это работает

Массив - это упорядоченный набор значений одного базового типа: integer[], text[], даже двумерный integer[][]. Внутри Postgres хранит его как единый объект с границами по каждому измерению. Индексация в SQL начинается с единицы, а не с нуля - это первая ловушка для тех, кто пришел из программирования. Массив удобен, когда порядок важен и набор короткий, например список номеров мест в одном бронировании.

Диапазон описывает непрерывный отрезок значений: int4range для целых, numrange для чисел, tsrange и tstzrange для времени, daterange для дат. У границы есть включенность: запись [10,20) означает от 10 включительно до 20 не включая. Сила range в том, что Postgres знает геометрию отрезка: умеет проверять пересечение оператором &&, вложенность, соседство. Это решает классическую задачу бронирования без перекрытий гораздо чище, чем пара колонок from и to с ручными проверками.

Перечисление (enum) - это собственный тип с фиксированным списком текстовых меток в заданном порядке: например статус билета. Внутри значение хранится компактно (4 байта на ссылку), сравнивается быстро, а порядок сортировки определяется порядком объявления меток, а не алфавитом. Enum защищает от опечаток на уровне типа: вставить статус которого нет в списке просто не получится.

Составной тип - это именованная строка из нескольких полей, по сути своя мини-таблица как тип данных. Каждая таблица автоматически порождает одноименный составной тип, но можно создать и отдельный через CREATE TYPE. Удобно, когда несколько полей логически слиплись в одну сущность, например структура адреса.

SQL и примеры

Массивы используем на демобазе Авиаперевозки. В таблице seats место описано номером, но соберем номера мест по салону самолета в массив:

Код: Выделить всё

SELECT aircraft_code,
       array_agg(seat_no ORDER BY seat_no) AS seats
FROM   seats
GROUP  BY aircraft_code;
array_agg собрал значения группы в один массив. Обратная операция - развернуть массив в строки через unnest:

Код: Выделить всё

SELECT unnest(ARRAY['A','B','C']) AS letter;
Полезные операторы: проверка вхождения элемента и пересечения массивов.

Код: Выделить всё

SELECT ARRAY[1,2,3] @> ARRAY[2]    AS contains_two,
       ARRAY[1,2] && ARRAY[2,9]    AS overlaps,
       cardinality(ARRAY[5,6,7])   AS len;
Здесь @> значит содержит, && значит есть общие элементы, cardinality возвращает число элементов. Под массивы с проверкой @> можно строить индекс GIN, и поиск по содержимому будет быстрым.

Диапазоны покажем на интервалах. Построим для каждого рейса временной отрезок от вылета до прилета и проверим пересечение с заданным окном:

Код: Выделить всё

SELECT flight_id,
       tstzrange(scheduled_departure, scheduled_arrival) AS window
FROM   flights
WHERE  tstzrange(scheduled_departure, scheduled_arrival)
       && tstzrange('2017-08-15 00:00+03','2017-08-15 06:00+03');
Оператор && вернет рейсы, чей отрезок пересекается с ночным окном. Чтобы запретить накладки на уровне базы, есть exclusion constraint:

Код: Выделить всё

CREATE TABLE room_booking (
    room_id int,
    during  tstzrange,
    EXCLUDE USING gist (room_id WITH =, during WITH &&)
);
База сама не даст вставить две брони одной комнаты с пересекающимся временем.

Перечисление: заведем тип статуса и используем порядок меток.

Код: Выделить всё

CREATE TYPE ticket_state AS ENUM ('booked','checked_in','boarded','cancelled');
SELECT 'boarded'::ticket_state > 'booked'::ticket_state AS later;
Сравнение работает по порядку объявления, поэтому boarded больше чем booked. Начиная с PG 10 новую метку можно добавить через ALTER TYPE ... ADD VALUE, а с PG 12 это работает и внутри транзакции. Удалить метку из enum штатно нельзя - это важное ограничение.

Составной тип соберем для адреса:

Код: Выделить всё

CREATE TYPE addr AS (city text, street text, zip text);
SELECT (ROW('Moscow','Tverskaya','101000')::addr).city AS city;
Поля достаются через точку, выражение со строкой надо брать в скобки. В колонке такого типа можно хранить весь адрес целиком.

Частые грабли
  • Индексация массива с единицы: arr[1] - первый элемент, arr[0] вернет NULL, а не ошибку.
  • Массив как замена связи многие-ко-многим: искать, джойнить и поддерживать ссылочную целостность по элементам массива неудобно, для этого нужна таблица связи.
  • Путаница с границами range: [a,b) и [a,b] это разные множества, по умолчанию нижняя включается, верхняя нет.
  • Пустой и NULL-диапазон - разные вещи: empty не содержит ничего, а NULL это отсутствие значения.
  • Удаление метки enum невозможно штатно, а порядок меток фиксируется при создании - меняется только через ALTER TYPE с оговорками.
  • ADD VALUE до PG 12 нельзя было применять внутри транзакционного блока вместе с использованием значения.
  • Для оператора && по диапазонам нужен GiST-индекс, обычный btree пересечение не ускорит.
  • Составной тип усложняет фильтры и индексы по отдельному вложенному полю - иногда проще обычные колонки.
Мини-лаба
  • Создайте таблицу events(id int, tags text[], slot tstzrange).
  • Вставьте 3 строки с разными тегами и пересекающимися и непересекающимися слотами времени.
  • Запросом найдите события, где tags содержит 'sql' через оператор @>.
  • Через unnest разверните теги в строки и посчитайте частоту каждого тега.
  • Найдите пары событий, чьи слоты пересекаются, используя && и self-join по id1 < id2.
  • Создайте тип priority AS ENUM ('low','mid','high'), добавьте колонку и отсортируйте события по приоритету.
  • Добавьте exclusion constraint, запрещающий пересечение слотов, и проверьте, что вставка-дубль падает.
Контрольные вопросы
  • С какого индекса начинается обращение к элементам массива в SQL и что вернет обращение за границу?
  • Чем отличаются операторы @> и && для массивов и для диапазонов?
  • Как граница диапазона [a,b) отличается от (a,b] по составу множества?
  • Почему сортировка enum не алфавитная и от чего зависит порядок?
  • Какие ограничения есть у изменения enum: что можно, а что нельзя сделать без пересоздания типа?
  • В каких случаях массив или составной тип лучше заменить отдельной таблицей?
👍3 ❤️ 🔥1 😄 🤔2
Аватара пользователя
sgribb
Сообщения: 1
Зарегистрирован: 15 май 2026, 11:02

Re: Массивы, диапазоны, enum, составные типы

Сообщение sgribb »

А зачем range, если можно две колонки from и to завести? Дошло только когда увидел EXCLUDE - база сама ловит накладки бронирований, руками такое не напишешь нормально
👍3 ❤️1 🔥 😄 🤔
Аватара пользователя
toradmin
Сообщения: 1
Зарегистрирован: 11 май 2026, 09:52

Re: Массивы, диапазоны, enum, составные типы

Сообщение toradmin »

осторожно с enum в проде: добавить значение легко, а удалить или порядок поменять - только пересоздавать тип, наелся этого на статусах заказов
👍 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Строки, текст, дата и время, таймзоны
Следующая глава →
JSON и JSONB: операторы и индексация

Все главы курса «PostgreSQL: от первого запроса до продакшена»

Поделиться темой: ✈ Telegram VK
  • Похожие темы
Похожие запросы: партиционирование таблиц postgresql по дате

Вернуться в «PostgreSQL: от первого запроса до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя