PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

PostgreSQL :incrémentation automatique basée sur une contrainte unique multi-colonnes

Ce serait bien si PostgreSQL supportait l'incrémentation "sur une colonne secondaire dans un index à plusieurs colonnes" comme les tables MyISAM de MySQL

Oui, mais notez que ce faisant, MyISAM verrouille toute votre table. Ce qui permet ensuite de trouver en toute sécurité le +1 le plus élevé sans se soucier des transactions simultanées.

Dans Postgres, vous pouvez également le faire, et sans verrouiller toute la table. Un verrou consultatif et un déclencheur suffiront :

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;

Cela donne :

  grp   | id |  name   
--------+----+---------
 fish   |  1 | lax
 mammal |  1 | dog
 mammal |  2 | cat
 mammal |  3 | whale
 bird   |  1 | penguin
 bird   |  2 | ostrich
(6 rows)

Il y a une mise en garde. Les verrous consultatifs sont maintenus jusqu'à leur libération ou jusqu'à l'expiration de la session. Si une erreur se produit lors de la transaction, le verrou est conservé et vous devez le libérer manuellement.

SELECT pg_advisory_unlock('animals'::regclass::int, i)
FROM generate_series(1, array_length(enum_range(NULL::animal_grp),1)) i;

Dans Postgres 9.1, vous pouvez supprimer le déclencheur de déverrouillage et remplacer l'appel pg_advisory_lock() par pg_advisory_xact_lock(). Celui-ci est automatiquement retenu jusqu'à et libéré à la fin de la transaction.

Sur une note séparée, je m'en tiendrais à une bonne vieille séquence. Cela accélérera les choses, même si ce n'est pas aussi joli lorsque vous regardez les données.

Enfin, une séquence unique par combo (année, mois) pourrait également être obtenue en ajoutant une table supplémentaire, dont la clé primaire est une série, et dont la valeur (année, mois) a une contrainte unique.