Би било хубаво PostgreSQL да поддържа увеличаване на "на вторична колона в индекс с множество колони" като MyISAM таблиците на 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(). Този автоматично се задържа до и се освобождава в края на транзакцията.
Като отделна бележка, бих се придържал към използването на добра стара последователност. Това ще направи нещата по-бързи – дори и да не изглежда толкова красиво, когато погледнете данните.
И накрая, уникална последователност за комбо (година, месец) може да се получи и чрез добавяне на допълнителна таблица, чийто първичен ключ е сериен и чиято стойност (година, месец) има уникално ограничение за него.