PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL:incremento automatico basato su un vincolo univoco multicolonna

Sarebbe bello se PostgreSQL supportasse l'incremento "su una colonna secondaria in un indice a più colonne" come le tabelle MyISAM di MySQL

Sì, ma tieni presente che così facendo, MyISAM blocca l'intero tavolo. Ciò rende sicuro trovare il +1 più grande senza preoccuparsi delle transazioni simultanee.

In Postgres puoi farlo anche tu e senza bloccare l'intero tavolo. Un blocco di avviso e un trigger saranno sufficienti:

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;

Questo produce:

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

C'è un avvertimento. I blocchi di avviso vengono mantenuti fino al rilascio o fino alla scadenza della sessione. Se si verifica un errore durante la transazione, il blocco viene mantenuto ed è necessario sbloccarlo manualmente.

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

In Postgres 9.1, puoi scartare il trigger di sblocco e sostituire la chiamata pg_advisory_lock() con pg_advisory_xact_lock(). Quello viene automaticamente trattenuto e rilasciato al termine della transazione.

In una nota a parte, mi atterrerei a usare una buona vecchia sequenza. Ciò renderà le cose più veloci, anche se non è così bello quando guardi i dati.

Infine, una sequenza unica per combo (anno, mese) può essere ottenuta anche aggiungendo una tabella extra, la cui chiave primaria è una seriale, e il cui valore (anno, mese) ha un vincolo univoco su di essa.