sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL:automatisch verhogen op basis van unieke beperking met meerdere kolommen

Het zou mooi zijn als PostgreSQL het verhogen van "op een secundaire kolom in een index met meerdere kolommen" zou ondersteunen, zoals MySQL's MyISAM-tabellen

Ja, maar houd er rekening mee dat MyISAM hierdoor uw hele tafel vergrendelt. Wat het vervolgens veilig maakt om de grootste +1 te vinden zonder je zorgen te maken over gelijktijdige transacties.

In Postgres kunt u dit ook doen, en zonder de hele tafel te vergrendelen. Een adviesblokkering en een trigger zijn goed genoeg:

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;

Dit levert:

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

Er is één voorbehoud. Adviesvergrendelingen worden vastgehouden totdat ze worden vrijgegeven of totdat de sessie verloopt. Als er een fout optreedt tijdens de transactie, blijft het slot in de buurt en moet u het handmatig ontgrendelen.

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 kunt u de ontgrendelingstrigger negeren en de pg_advisory_lock()-aanroep vervangen door pg_advisory_xact_lock(). Die wordt automatisch vastgehouden tot en vrijgegeven aan het einde van de transactie.

Op een aparte opmerking, ik zou vasthouden aan het gebruik van een goede oude reeks. Dat maakt dingen sneller -- zelfs als het er niet zo mooi uitziet als je naar de gegevens kijkt.

Ten slotte zou een unieke reeks per combo (jaar, maand) ook kunnen worden verkregen door een extra tabel toe te voegen, waarvan de primaire sleutel een serienummer is en waarvan de waarde (jaar, maand) een unieke beperking heeft.



  1. Is het mogelijk om een ​​recursieve SQL-query te maken?

  2. Hoe krijg ik een kolomattributenquery uit de tabelnaam met behulp van PostgreSQL?

  3. SQL Server 2005 DateAdd gebruiken om een ​​dag aan een datum toe te voegen

  4. TUPLES gebruiken om meer dan 1000 vermeldingen in de SQL IN-clausule te plaatsen