PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

PostgreSQL:Automatisches Inkrement basierend auf mehrspaltiger eindeutiger Einschränkung

Es wäre schön, wenn PostgreSQL das Inkrementieren "auf einer sekundären Spalte in einem mehrspaltigen Index" wie MyISAM-Tabellen von MySQL unterstützen würde

Ja, aber beachten Sie, dass MyISAM dabei Ihre gesamte Tabelle sperrt. Das macht es dann sicher, die größten +1 zu finden, ohne sich Gedanken über gleichzeitige Transaktionen machen zu müssen.

In Postgres können Sie dies auch tun, ohne die gesamte Tabelle zu sperren. Eine beratende Sperre und ein Auslöser reichen aus:

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;

Dies ergibt:

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

Es gibt eine Einschränkung. Beratungssperren werden gehalten, bis sie freigegeben werden oder bis die Sitzung abläuft. Wenn während der Transaktion ein Fehler auftritt, bleibt die Sperre bestehen und Sie müssen sie manuell aufheben.

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 können Sie den Unlock-Trigger verwerfen und den pg_advisory_lock()-Aufruf durch pg_advisory_xact_lock() ersetzen. Dieser wird automatisch bis zum Ende der Transaktion gehalten und freigegeben.

In einer separaten Anmerkung würde ich bei der Verwendung einer guten alten Sequenz bleiben. Das wird die Dinge schneller machen – auch wenn es nicht so hübsch aussieht, wenn man sich die Daten ansieht.

Schließlich könnte eine eindeutige Sequenz pro (Jahr, Monat)-Kombination auch erhalten werden, indem eine zusätzliche Tabelle hinzugefügt wird, deren Primärschlüssel eine Seriennummer ist und deren (Jahr, Monat)-Wert eine eindeutige Einschränkung hat.