Было бы неплохо, если бы PostgreSQL поддерживал увеличение "на вторичном столбце в индексе с несколькими столбцами", как в MySQL-таблицах MySQL
Да, но учтите, что при этом MyISAM блокирует всю вашу таблицу. Что делает безопасным поиск наибольшего +1, не беспокоясь о параллельных транзакциях.
В Postgres вы можете сделать это и без блокировки всей таблицы. Консультативный замок и триггер будут достаточно хороши:
CREATE TYPE animal_grp AS ENUM ('fish','mammal','bird');
CREATE TABLE animals (
grp animal_grp NOT NULL,
id INT NOT NULL DEFAULT 0,
name varchar NOT NULL,
PRIMARY KEY (grp,id)
);
CREATE OR REPLACE FUNCTION animals_id_auto()
RETURNS trigger AS $$
DECLARE
_rel_id constant int := 'animals'::regclass::int;
_grp_id int;
BEGIN
_grp_id = array_length(enum_range(NULL, NEW.grp), 1);
-- Obtain an advisory lock on this table/group.
PERFORM pg_advisory_lock(_rel_id, _grp_id);
SELECT COALESCE(MAX(id) + 1, 1)
INTO NEW.id
FROM animals
WHERE grp = NEW.grp;
RETURN NEW;
END;
$$ LANGUAGE plpgsql STRICT;
CREATE TRIGGER animals_id_auto
BEFORE INSERT ON animals
FOR EACH ROW WHEN (NEW.id = 0)
EXECUTE PROCEDURE animals_id_auto();
CREATE OR REPLACE FUNCTION animals_id_auto_unlock()
RETURNS trigger AS $$
DECLARE
_rel_id constant int := 'animals'::regclass::int;
_grp_id int;
BEGIN
_grp_id = array_length(enum_range(NULL, NEW.grp), 1);
-- Release the lock.
PERFORM pg_advisory_unlock(_rel_id, _grp_id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql STRICT;
CREATE TRIGGER animals_id_auto_unlock
AFTER INSERT ON animals
FOR EACH ROW
EXECUTE PROCEDURE animals_id_auto_unlock();
INSERT INTO animals (grp,name) VALUES
('mammal','dog'),('mammal','cat'),
('bird','penguin'),('fish','lax'),('mammal','whale'),
('bird','ostrich');
SELECT * FROM animals ORDER BY grp,id;
Это дает:
grp | id | name
--------+----+---------
fish | 1 | lax
mammal | 1 | dog
mammal | 2 | cat
mammal | 3 | whale
bird | 1 | penguin
bird | 2 | ostrich
(6 rows)
Есть одна оговорка. Консультативные блокировки удерживаются до освобождения или до истечения сеанса. Если во время транзакции возникает ошибка, блокировка сохраняется, и вам необходимо снять ее вручную.
SELECT pg_advisory_unlock('animals'::regclass::int, i)
FROM generate_series(1, array_length(enum_range(NULL::animal_grp),1)) i;
В Postgres 9.1 вы можете сбросить триггер разблокировки и заменить вызов pg_advisory_lock () на pg_advisory_xact_lock (). Этот автоматически удерживается до и освобождается в конце транзакции.
В отдельном примечании я бы придерживался старой доброй последовательности. Это ускорит процесс, даже если вы посмотрите на данные не так привлекательно.
Наконец, уникальная последовательность для комбинированного списка (год, месяц) также может быть получена путем добавления дополнительной таблицы, первичный ключ которой является последовательным, а значение (год, месяц) которой имеет уникальное ограничение.