sql >> Databasteknik >  >> RDS >> PostgreSQL

PostgreSQL:Automatisk ökning baserat på unik begränsning med flera kolumner

Det skulle vara trevligt om PostgreSQL stödde inkrementering "på en sekundär kolumn i ett index med flera kolumner" som MySQL:s MyISAM-tabeller

Ja, men notera att genom att göra det låser MyISAM hela ditt bord. Vilket gör det säkert att hitta den största +1:an utan att behöva oroa dig för samtidiga transaktioner.

I Postgres kan du göra detta också, och utan att låsa hela bordet. Ett rådgivande lås och en trigger kommer att vara tillräckligt bra:

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;

Detta ger:

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

Det finns en varning. Rådgivande lås hålls tills de släpps eller tills sessionen löper ut. Om ett fel uppstår under transaktionen hålls låset kvar och du måste släppa det manuellt.

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

I Postgres 9.1 kan du ignorera upplåsningsutlösaren och ersätta pg_advisory_lock()-anropet med pg_advisory_xact_lock(). Den hålls automatiskt kvar tills och släpps i slutet av transaktionen.

På en separat notering, skulle jag hålla mig till att använda en gammal bra sekvens. Det kommer att göra saker snabbare – även om det inte är lika snyggt när du tittar på data.

Slutligen kan en unik sekvens per (år, månad) kombination också erhållas genom att lägga till en extra tabell, vars primärnyckel är en seriell, och vars (år, månad) värde har en unik begränsning.



  1. Filtrerade index och INKLUDERADE kolumner

  2. Rethink Flask – En enkel att göra-lista som drivs av Flask och RethinkDB

  3. Ostrukturerat innehåll:en outnyttjad bränslekälla för AI och maskininlärning

  4. Specialtecken i PHP/MySQL