Bylo by hezké, kdyby PostgreSQL podporoval inkrementaci „na sekundárním sloupci v indexu s více sloupci“, jako jsou tabulky MyISAM MySQL
Jo, ale všimněte si, že tím MyISAM uzamkne celý váš stůl. Díky tomu můžete bezpečně najít největší +1 bez obav ze souběžných transakcí.
V Postgres to můžete udělat také a bez zamykání celého stolu. Poradní zámek a spoušť budou dost dobré:
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;
Výsledkem je:
grp | id | name
--------+----+---------
fish | 1 | lax
mammal | 1 | dog
mammal | 2 | cat
mammal | 3 | whale
bird | 1 | penguin
bird | 2 | ostrich
(6 rows)
Existuje jedno upozornění. Poradní zámky jsou drženy, dokud se neuvolní nebo dokud nevyprší relace. Pokud během transakce dojde k chybě, zámek zůstane zachován a musíte jej uvolnit ručně.
SELECT pg_advisory_unlock('animals'::regclass::int, i)
FROM generate_series(1, array_length(enum_range(NULL::animal_grp),1)) i;
V Postgresu 9.1 můžete zahodit spouštěč odemknutí a nahradit volání pg_advisory_lock() voláním pg_advisory_xact_lock(). Ten je automaticky zadržen a uvolněn na konci transakce.
Na samostatnou poznámku bych se držel použití staré dobré sekvence. To vše urychlí – i když to při pohledu na data nevypadá tak pěkně.
A konečně, jedinečnou sekvenci pro (rok, měsíc) combo lze také získat přidáním další tabulky, jejíž primární klíč je sériový a jejíž hodnota (rok, měsíc) má jedinečné omezení.