SQL SEQUENCE в PostgreSQL: один генератор ID для нескольких таблиц

Когда требуется генерировать уникальные идентификаторы в PostgreSQL, первое что приходит в голову — это SERIAL. Этот механизм прост в использовании и отлично справляется с задачей в большинстве случаев. Но что делать, если возникает задача посложнее?

Представим ситуацию: нужно создать систему нумерации документов, где номера должны быть общими для нескольких таблиц.

Например, счета, накладные и акты должны нумероваться из единого числового ряда: 100001, 100002, 100003... Попытка использовать отдельный SERIAL для каждой таблицы приведёт к дублированию номеров, а это недопустимо 🚫

Именно здесь на сцену выходят SEQUENCE — мощный инструмент для генерации уникальных идентификаторов с гибкостью, которую не может предоставить SERIAL.

Что такое SEQUENCE?

SEQUENCE — это специальный объект базы данных, который генерирует упорядоченную серию уникальных числовых значений по определённому правилу.

Представьте себе автоматический нумератор документов в офисе: каждый раз, когда приходит новый документ, нумератор выдаёт следующий номер. SEQUENCE работает точно так же, но в базе данных.

Простой пример: у вас есть счётчик, который стоит на значении 1000. Когда вы просите "дай следующее число", он выдаёт 1000 и увеличивается до 1001. В следующий раз он выдаст 1001, затем 1002, и так далее. Просто и надёжно! ✨

Зачем нужны SEQUENCE?

SEQUENCE даёт гораздо больше контроля и гибкости, чем SERIAL. Вот ключевые преимущества:

  • 🔗 Независимость от таблиц
    Один SEQUENCE можно использовать для генерации ID сразу в нескольких таблицах. Например, счета, накладные и акты могут нумероваться из единого числового ряда — это гарантирует уникальность номеров документов во всей системе.

  • 🎨 Гибкость настройки
    Можно начать с любого числа (например, 1000), использовать обратный отсчёт, настроить циклическую нумерацию или изменить шаг приращения.

Создание SEQUENCE

SEQUENCE создаётся с помощью команды CREATE SEQUENCE.

Базовый синтаксис

PostgreSQL 17.5
CREATE SEQUENCE имя_последовательности
    [START WITH начальное_значение]
    [INCREMENT BY шаг_приращения]
    [MINVALUE минимальное_значение]
    [MAXVALUE максимальное_значение]
    [CYCLE | NO CYCLE]
    [CACHE количество];

Параметры SEQUENCE

ПараметрОписаниеПо умолчанию
START WITHНачальное значение последовательности1
INCREMENT BYШаг изменения (может быть отрицательным)1
MINVALUEМинимальное значение1
MAXVALUEМаксимальное значение9223372036854775807
CYCLEНачинать заново при достижении пределаNO CYCLE
CACHEКоличество предварительно выделенных значений1

Например, SEQUENCE для генерации номеров заказов, начиная с 1000, может выглядеть так:

PostgreSQL 17.5
CREATE SEQUENCE order_id_seq
    START WITH 1000
    INCREMENT BY 1
    NO CYCLE;

Функции для работы с SEQUENCE

PostgreSQL предоставляет несколько функций для работы с SEQUENCE:

nextval() — получить следующее значение

PostgreSQL 17.5
SELECT nextval('order_id_seq');

Эта функция увеличивает счётчик и возвращает новое значение. ⬆️

nextval
1000

При повторном вызове:

PostgreSQL 17.5
SELECT nextval('order_id_seq');
nextval
1001

currval() — получить текущее значение

PostgreSQL 17.5
SELECT currval('order_id_seq');

Возвращает последнее значение, полученное через nextval() в текущей сессии, без изменения счётчика. 👀

⚠️ Важная ловушка! При первом использовании currval() можно получить ошибку "currval of sequence is not yet defined in this session". Причина: currval() можно вызвать только после хотя бы одного вызова nextval() в той же сессии!

Логика проста: нельзя узнать "текущее" значение, если оно ещё ни разу не было запрошено в рамках текущей сессии. 🤔

setval() — установить значение

PostgreSQL 17.5
SELECT setval('order_id_seq', 5000);

Устанавливает текущее значение SEQUENCE. Следующий вызов nextval() вернёт 5001. 🎯

Применение в таблицах

Способ 1: Использование DEFAULT

Самый распространённый способ — использовать SEQUENCE как значение по умолчанию:

PostgreSQL 17.5
CREATE TABLE Orders (
    order_id INTEGER DEFAULT nextval('order_id_seq') PRIMARY KEY,
    customer_name VARCHAR(100),
    order_date DATE
);

При вставке записи значение order_id будет автоматически заполнено:

PostgreSQL 17.5
INSERT INTO Orders (customer_name, order_date)
VALUES ('Иван Петров', '2025-11-14');
order_idcustomer_nameorder_date
1000Иван Петров2025-11-14

Способ 2: Тип SERIAL

PostgreSQL предлагает удобный тип SERIAL, который автоматически создаёт SEQUENCE: 💡

PostgreSQL 17.5
CREATE TABLE Products (
    product_id SERIAL PRIMARY KEY,
    product_name VARCHAR(100),
    price DECIMAL(10, 2)
);

Это эквивалентно:

PostgreSQL 17.5
CREATE SEQUENCE products_product_id_seq;

CREATE TABLE Products (
    product_id INTEGER DEFAULT nextval('products_product_id_seq') PRIMARY KEY,
    product_name VARCHAR(100),
    price DECIMAL(10, 2)
);

Управление SEQUENCE

  • Просмотр информации о SEQUENCE

    PostgreSQL 17.5
    SELECT * FROM information_schema.sequences
    WHERE sequence_name = 'order_id_seq';
    

    Или более детально через системную функцию:

    PostgreSQL 17.5
    SELECT last_value, is_called FROM order_id_seq;
    
  • Изменение параметров SEQUENCE

    PostgreSQL 17.5
    ALTER SEQUENCE order_id_seq
        INCREMENT BY 10
        RESTART WITH 2000;
    
  • Удаление SEQUENCE

    PostgreSQL 17.5
    DROP SEQUENCE order_id_seq;
    

    ⚠️ Внимание: Если SEQUENCE используется в таблице как значение по умолчанию, сначала нужно удалить это ограничение или использовать CASCADE:

    PostgreSQL 17.5
    DROP SEQUENCE order_id_seq CASCADE;
    

SEQUENCE для нескольких таблиц

Один SEQUENCE можно использовать для нескольких таблиц. Это особенно полезно для систем документооборота, где нужна сквозная нумерация:

PostgreSQL 17.5
CREATE SEQUENCE document_number_seq START WITH 100000;

CREATE TABLE Invoices (
    invoice_id SERIAL PRIMARY KEY,
    document_number INTEGER DEFAULT nextval('document_number_seq') UNIQUE,
    customer_id INTEGER,
    amount DECIMAL(10, 2)
);

CREATE TABLE DeliveryNotes (
    note_id SERIAL PRIMARY KEY,
    document_number INTEGER DEFAULT nextval('document_number_seq') UNIQUE,
    order_id INTEGER,
    delivery_date DATE
);

CREATE TABLE Acts (
    act_id SERIAL PRIMARY KEY,
    document_number INTEGER DEFAULT nextval('document_number_seq') UNIQUE,
    contract_id INTEGER,
    act_date DATE
);

Теперь все документы (счета, накладные, акты) будут иметь уникальные номера во всей системе документооборота. 🌐

Интерактивная схема работы SEQUENCE

SEQUENCE: document_number_seq
100000
Invoices (Счета)
DeliveryNotes (Накладные)
Acts (Акты)

Продвинутые техники

  • SEQUENCE с кэшированием

    Для повышения производительности можно использовать кэширование:

    PostgreSQL 17.5
    CREATE SEQUENCE fast_seq
        START WITH 1
        INCREMENT BY 1
        CACHE 100;
    

    База данных предварительно выделит 100 значений в памяти, что ускорит их выдачу. ⚡

  • Циклический SEQUENCE

    Для случаев, когда нужна циклическая нумерация (например, дни недели):

    PostgreSQL 17.5
    CREATE SEQUENCE day_of_week_seq
        START WITH 1
        INCREMENT BY 1
        MINVALUE 1
        MAXVALUE 7
        CYCLE;
    

    После достижения 7 SEQUENCE вернётся к 1. 🔄

  • Убывающий SEQUENCE

    PostgreSQL 17.5
    CREATE SEQUENCE countdown_seq
        START WITH 100
        INCREMENT BY -1
        MINVALUE 1
        NO CYCLE;
    

    Этот SEQUENCE будет генерировать: 100, 99, 98, 97... ⬇️

На что обратить внимание

  • В 90% случаев вам достаточно SERIAL
    Не создавайте отдельный SEQUENCE, если вам не нужна его специфическая гибкость. SERIAL проще и справляется с большинством задач.

  • Последовательности имеют пропуски 🕳️
    Не ждите, что номера будут идти без пробелов: 1, 2, 3, 4... Откаты транзакций, параллельные запросы, кэширование — всё это создаёт пропуски. Если встретите "заказ №102 после №99" — это нормально.

    Если действительно нужна непрерывная нумерация (например, для счетов-фактур по требованиям бухгалтерии), SEQUENCE — не лучший выбор.

  • Забытый MAXVALUE — бомба замедленного действия 💥
    Установили MAXVALUE 999999 и забыли? После миллионной записи всё встанет. Для production всегда используйте максимально возможное значение или мониторьте приближение к пределу.

Подводя итоги

Для большинства задач SERIAL справляется отлично. Но если вам нужна общая нумерация для нескольких таблиц, специфический шаг приращения или возможность получить ID до вставки — SEQUENCE решает эти задачи элегантно.

Главное — не забывайте про пропуски в нумерации и следите за MAXVALUE. В остальном это простой и мощный инструмент! 🚀